-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix up docs and other othttp cleanups
- Loading branch information
Showing
6 changed files
with
272 additions
and
310 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2019, OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package othttp provides a http.Handler and functions that are | ||
// intended to be used to add tracing by wrapping | ||
// existing handlers (with Handler) and routes WithRouteTag. | ||
package othttp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// Copyright 2019, OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package othttp | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
|
||
"go.opentelemetry.io/api/core" | ||
"go.opentelemetry.io/api/propagation" | ||
"go.opentelemetry.io/api/trace" | ||
prop "go.opentelemetry.io/propagation" | ||
) | ||
|
||
var _ http.Handler = &Handler{} | ||
|
||
// Attribute keys that the Handler can add to a span. | ||
var ( | ||
HostKey = core.Key("http.host") // the http host (http.Request.Host) | ||
MethodKey = core.Key("http.method") // the http method (http.Request.Method) | ||
PathKey = core.Key("http.path") // the http path (http.Request.URL.Path) | ||
URLKey = core.Key("http.url") // the http url (http.Request.URL.String()) | ||
UserAgentKey = core.Key("http.user_agent") // the http user agent (http.Request.UserAgent()) | ||
RouteKey = core.Key("http.route") // the http route (ex: /users/:id) | ||
StatusCodeKey = core.Key("http.status_code") // if set, the http status | ||
ReadBytesKey = core.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read | ||
ReadErrorKey = core.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded) | ||
WroteBytesKey = core.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written | ||
WriteErrorKey = core.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) | ||
) | ||
|
||
// Handler is http middleware that corresponds to the http.Handler interface and | ||
// is designed to wrap a http.Mux (or equivalent), while individual routes on | ||
// the mux are wrapped with WithRouteTag. A Handler will add various attributes | ||
// to the span using the core.Keys defined in this package. | ||
type Handler struct { | ||
operation string | ||
handler http.Handler | ||
|
||
tracer trace.Tracer | ||
prop propagation.TextFormatPropagator | ||
spanOptions []trace.SpanOption | ||
public bool | ||
readEvent bool | ||
writeEvent bool | ||
} | ||
|
||
// Option function used for setting *optional* Handler properties | ||
type Option func(*Handler) | ||
|
||
// WithTracer configures the Handler with a specific tracer. If this option | ||
// isn't specified then the global tracer is used. | ||
func WithTracer(tracer trace.Tracer) Option { | ||
return func(h *Handler) { | ||
h.tracer = tracer | ||
} | ||
} | ||
|
||
// WithPublicEndpoint configures the Handler to link the span with an incoming | ||
// span context. If this option is not provided, then the association is a child | ||
// association instead of a link. | ||
func WithPublicEndpoint() Option { | ||
return func(h *Handler) { | ||
h.public = true | ||
} | ||
} | ||
|
||
// WithPropagator configures the Handler with a specific propagator. If this | ||
// option isn't specificed then | ||
// go.opentelemetry.io/propagation.HTTPTraceContextPropagator is used. | ||
func WithPropagator(p propagation.TextFormatPropagator) Option { | ||
return func(h *Handler) { | ||
h.prop = p | ||
} | ||
} | ||
|
||
// WithSpanOptions configures the Handler with an additional set of | ||
// trace.SpanOptions, which are applied to each new span. | ||
func WithSpanOptions(opts ...trace.SpanOption) Option { | ||
return func(h *Handler) { | ||
h.spanOptions = opts | ||
} | ||
} | ||
|
||
type event int | ||
|
||
// Different types of events that can be recorded, see WithMessageEvents | ||
const ( | ||
ReadEvents event = iota | ||
WriteEvents | ||
) | ||
|
||
// WithMessageEvents configures the Handler to record the specified events | ||
// (span.AddEvent) on spans. By default only summary attributes are added at the | ||
// end of the request. | ||
// | ||
// Valid events are: | ||
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read | ||
// using the ReadBytesKey | ||
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write | ||
// using the WriteBytesKey | ||
func WithMessageEvents(events ...event) Option { | ||
return func(h *Handler) { | ||
for _, e := range events { | ||
switch e { | ||
case ReadEvents: | ||
h.readEvent = true | ||
case WriteEvents: | ||
h.writeEvent = true | ||
} | ||
} | ||
} | ||
} | ||
|
||
// NewHandler wraps the passed handler, functioning like middleware, in a span | ||
// named after the operation and with any provided HandlerOptions. | ||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { | ||
h := Handler{handler: handler, operation: operation} | ||
defaultOpts := []Option{ | ||
WithTracer(trace.GlobalTracer()), | ||
WithPropagator(prop.HTTPTraceContextPropagator{}), | ||
} | ||
|
||
for _, opt := range append(defaultOpts, opts...) { | ||
opt(&h) | ||
} | ||
return &h | ||
} | ||
|
||
// ServeHTTP serves HTTP requests (http.Handler) | ||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
opts := append([]trace.SpanOption{}, h.spanOptions...) // start with the configured options | ||
|
||
sc := h.prop.Extract(r.Context(), r.Header) | ||
if sc.IsValid() { // not a valid span context, so no link / parent relationship to establish | ||
var opt trace.SpanOption | ||
if h.public { | ||
// TODO: If the endpoint is a public endpoint, it should start a new trace | ||
// and incoming remote sctx should be added as a link | ||
// (WithLinks(links...), this option doesn't exist yet). Replace ChildOf | ||
// below with something like: opt = trace.WithLinks(sc) | ||
opt = trace.ChildOf(sc) | ||
} else { // not a private endpoint, so assume child relationship | ||
opt = trace.ChildOf(sc) | ||
} | ||
opts = append(opts, opt) | ||
} | ||
|
||
ctx, span := h.tracer.Start(r.Context(), h.operation, opts...) | ||
defer span.End() | ||
|
||
readRecordFunc := func(int64) {} | ||
if h.readEvent { | ||
readRecordFunc = func(n int64) { | ||
span.AddEvent(ctx, "read", ReadBytesKey.Int64(n)) | ||
} | ||
} | ||
bw := bodyWrapper{ReadCloser: r.Body, record: readRecordFunc} | ||
r.Body = &bw | ||
|
||
writeRecordFunc := func(int64) {} | ||
if h.writeEvent { | ||
writeRecordFunc = func(n int64) { | ||
span.AddEvent(ctx, "write", WroteBytesKey.Int64(n)) | ||
} | ||
} | ||
|
||
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, injector: h.prop} | ||
|
||
// Setup basic span attributes before calling handler.ServeHTTP so that they | ||
// are available to be mutated by the handler if needed. | ||
span.SetAttributes( | ||
HostKey.String(r.Host), | ||
MethodKey.String(r.Method), | ||
PathKey.String(r.URL.Path), | ||
URLKey.String(r.URL.String()), | ||
UserAgentKey.String(r.UserAgent()), | ||
) | ||
|
||
h.handler.ServeHTTP(rww, r.WithContext(ctx)) | ||
|
||
setAfterServeAttributes(span, bw.read, rww.written, int64(rww.statusCode), bw.err, rww.err) | ||
} | ||
|
||
func setAfterServeAttributes(span trace.Span, read, wrote, statusCode int64, rerr, werr error) { | ||
kv := make([]core.KeyValue, 0, 5) | ||
// TODO: Consider adding an event after each read and write, possibly as an | ||
// option (defaulting to off), so as to not create needlessly verbose spans. | ||
if read > 0 { | ||
kv = append(kv, ReadBytesKey.Int64(read)) | ||
} | ||
if rerr != nil && rerr != io.EOF { | ||
kv = append(kv, ReadErrorKey.String(rerr.Error())) | ||
} | ||
if wrote > 0 { | ||
kv = append(kv, WroteBytesKey.Int64(wrote)) | ||
} | ||
if statusCode > 0 { | ||
kv = append(kv, StatusCodeKey.Int64(statusCode)) | ||
} | ||
if werr != nil && werr != io.EOF { | ||
kv = append(kv, WriteErrorKey.String(werr.Error())) | ||
} | ||
span.SetAttributes(kv...) | ||
} | ||
|
||
// WithRouteTag annotates a span with the provided route name using the | ||
// RouteKey Tag. | ||
func WithRouteTag(route string, h http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
span := trace.CurrentSpan(r.Context()) | ||
//TODO: Why doesn't tag.Upsert work? | ||
span.SetAttribute(RouteKey.String(route)) | ||
h.ServeHTTP(w, r.WithContext(trace.SetCurrentSpan(r.Context(), span))) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.