// Copyright 2018, OpenCensus 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 ochttp import ( "context" "io" "net/http" "strconv" "sync" "time" "go.opencensus.io/stats" "go.opencensus.io/tag" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" ) // Handler is an http.Handler wrapper to instrument your HTTP server with // OpenCensus. It supports both stats and tracing. // // Tracing // // This handler is aware of the incoming request's span, reading it from request // headers as configured using the Propagation field. // The extracted span can be accessed from the incoming request's // context. // // span := trace.FromContext(r.Context()) // // The server span will be automatically ended at the end of ServeHTTP. type Handler struct { // Propagation defines how traces are propagated. If unspecified, // B3 propagation will be used. Propagation propagation.HTTPFormat // Handler is the handler used to handle the incoming request. Handler http.Handler // StartOptions are applied to the span started by this Handler around each // request. // // StartOptions.SpanKind will always be set to trace.SpanKindServer // for spans started by this transport. StartOptions trace.StartOptions // GetStartOptions allows to set start options per request. If set, // StartOptions is going to be ignored. GetStartOptions func(*http.Request) trace.StartOptions // IsPublicEndpoint should be set to true for publicly accessible HTTP(S) // servers. If true, any trace metadata set on the incoming request will // be added as a linked trace instead of being added as a parent of the // current trace. IsPublicEndpoint bool // FormatSpanName holds the function to use for generating the span name // from the information found in the incoming HTTP Request. By default the // name equals the URL Path. FormatSpanName func(*http.Request) string } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var tags addedTags r, traceEnd := h.startTrace(w, r) defer traceEnd() w, statsEnd := h.startStats(w, r) defer statsEnd(&tags) handler := h.Handler if handler == nil { handler = http.DefaultServeMux } r = r.WithContext(context.WithValue(r.Context(), addedTagsKey{}, &tags)) handler.ServeHTTP(w, r) } func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Request, func()) { if isHealthEndpoint(r.URL.Path) { return r, func() {} } var name string if h.FormatSpanName == nil { name = spanNameFromURL(r) } else { name = h.FormatSpanName(r) } ctx := r.Context() startOpts := h.StartOptions if h.GetStartOptions != nil { startOpts = h.GetStartOptions(r) } var span *trace.Span sc, ok := h.extractSpanContext(r) if ok && !h.IsPublicEndpoint { ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc, trace.WithSampler(startOpts.Sampler), trace.WithSpanKind(trace.SpanKindServer)) } else { ctx, span = trace.StartSpan(ctx, name, trace.WithSampler(startOpts.Sampler), trace.WithSpanKind(trace.SpanKindServer), ) if ok { span.AddLink(trace.Link{ TraceID: sc.TraceID, SpanID: sc.SpanID, Type: trace.LinkTypeParent, Attributes: nil, }) } } span.AddAttributes(requestAttrs(r)...) if r.Body == nil { // TODO: Handle cases where ContentLength is not set. } else if r.ContentLength > 0 { span.AddMessageReceiveEvent(0, /* TODO: messageID */ r.ContentLength, -1) } return r.WithContext(ctx), span.End } func (h *Handler) extractSpanContext(r *http.Request) (trace.SpanContext, bool) { if h.Propagation == nil { return defaultFormat.SpanContextFromRequest(r) } return h.Propagation.SpanContextFromRequest(r) } func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func(tags *addedTags)) { ctx, _ := tag.New(r.Context(), tag.Upsert(Host, r.Host), tag.Upsert(Path, r.URL.Path), tag.Upsert(Method, r.Method)) track := &trackingResponseWriter{ start: time.Now(), ctx: ctx, writer: w, } if r.Body == nil { // TODO: Handle cases where ContentLength is not set. track.reqSize = -1 } else if r.ContentLength > 0 { track.reqSize = r.ContentLength } stats.Record(ctx, ServerRequestCount.M(1)) return track.wrappedResponseWriter(), track.end } type trackingResponseWriter struct { ctx context.Context reqSize int64 respSize int64 start time.Time statusCode int statusLine string endOnce sync.Once writer http.ResponseWriter } // Compile time assertion for ResponseWriter interface var _ http.ResponseWriter = (*trackingResponseWriter)(nil) func (t *trackingResponseWriter) end(tags *addedTags) { t.endOnce.Do(func() { if t.statusCode == 0 { t.statusCode = 200 } span := trace.FromContext(t.ctx) span.SetStatus(TraceStatus(t.statusCode, t.statusLine)) span.AddAttributes(trace.Int64Attribute(StatusCodeAttribute, int64(t.statusCode))) m := []stats.Measurement{ ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)), ServerResponseBytes.M(t.respSize), } if t.reqSize >= 0 { m = append(m, ServerRequestBytes.M(t.reqSize)) } allTags := make([]tag.Mutator, len(tags.t)+1) allTags[0] = tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)) copy(allTags[1:], tags.t) stats.RecordWithTags(t.ctx, allTags, m...) }) } func (t *trackingResponseWriter) Header() http.Header { return t.writer.Header() } func (t *trackingResponseWriter) Write(data []byte) (int, error) { n, err := t.writer.Write(data) t.respSize += int64(n) // Add message event for request bytes sent. span := trace.FromContext(t.ctx) span.AddMessageSendEvent(0 /* TODO: messageID */, int64(n), -1) return n, err } func (t *trackingResponseWriter) WriteHeader(statusCode int) { t.writer.WriteHeader(statusCode) t.statusCode = statusCode t.statusLine = http.StatusText(t.statusCode) } // wrappedResponseWriter returns a wrapped version of the original // ResponseWriter and only implements the same combination of additional // interfaces as the original. // This implementation is based on https://github.com/felixge/httpsnoop. func (t *trackingResponseWriter) wrappedResponseWriter() http.ResponseWriter { var ( hj, i0 = t.writer.(http.Hijacker) cn, i1 = t.writer.(http.CloseNotifier) pu, i2 = t.writer.(http.Pusher) fl, i3 = t.writer.(http.Flusher) rf, i4 = t.writer.(io.ReaderFrom) ) switch { case !i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter }{t} case !i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter io.ReaderFrom }{t, rf} case !i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Flusher }{t, fl} case !i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Flusher io.ReaderFrom }{t, fl, rf} case !i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Pusher }{t, pu} case !i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Pusher io.ReaderFrom }{t, pu, rf} case !i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Pusher http.Flusher }{t, pu, fl} case !i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Pusher http.Flusher io.ReaderFrom }{t, pu, fl, rf} case !i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier }{t, cn} case !i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier io.ReaderFrom }{t, cn, rf} case !i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher }{t, cn, fl} case !i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher io.ReaderFrom }{t, cn, fl, rf} case !i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher }{t, cn, pu} case !i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher io.ReaderFrom }{t, cn, pu, rf} case !i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher }{t, cn, pu, fl} case !i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{t, cn, pu, fl, rf} case i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker }{t, hj} case i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker io.ReaderFrom }{t, hj, rf} case i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Flusher }{t, hj, fl} case i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Flusher io.ReaderFrom }{t, hj, fl, rf} case i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher }{t, hj, pu} case i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher io.ReaderFrom }{t, hj, pu, rf} case i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher }{t, hj, pu, fl} case i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher io.ReaderFrom }{t, hj, pu, fl, rf} case i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier }{t, hj, cn} case i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier io.ReaderFrom }{t, hj, cn, rf} case i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher }{t, hj, cn, fl} case i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher io.ReaderFrom }{t, hj, cn, fl, rf} case i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher }{t, hj, cn, pu} case i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher io.ReaderFrom }{t, hj, cn, pu, rf} case i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher }{t, hj, cn, pu, fl} case i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{t, hj, cn, pu, fl, rf} default: return struct { http.ResponseWriter }{t} } }