1 // Copyright 2018, OpenCensus Authors
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
25 "go.opencensus.io/stats"
26 "go.opencensus.io/tag"
27 "go.opencensus.io/trace"
28 "go.opencensus.io/trace/propagation"
31 // Handler is an http.Handler wrapper to instrument your HTTP server with
32 // OpenCensus. It supports both stats and tracing.
36 // This handler is aware of the incoming request's span, reading it from request
37 // headers as configured using the Propagation field.
38 // The extracted span can be accessed from the incoming request's
41 // span := trace.FromContext(r.Context())
43 // The server span will be automatically ended at the end of ServeHTTP.
45 // Propagation defines how traces are propagated. If unspecified,
46 // B3 propagation will be used.
47 Propagation propagation.HTTPFormat
49 // Handler is the handler used to handle the incoming request.
52 // StartOptions are applied to the span started by this Handler around each
55 // StartOptions.SpanKind will always be set to trace.SpanKindServer
56 // for spans started by this transport.
57 StartOptions trace.StartOptions
59 // GetStartOptions allows to set start options per request. If set,
60 // StartOptions is going to be ignored.
61 GetStartOptions func(*http.Request) trace.StartOptions
63 // IsPublicEndpoint should be set to true for publicly accessible HTTP(S)
64 // servers. If true, any trace metadata set on the incoming request will
65 // be added as a linked trace instead of being added as a parent of the
69 // FormatSpanName holds the function to use for generating the span name
70 // from the information found in the incoming HTTP Request. By default the
71 // name equals the URL Path.
72 FormatSpanName func(*http.Request) string
75 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
77 r, traceEnd := h.startTrace(w, r)
79 w, statsEnd := h.startStats(w, r)
83 handler = http.DefaultServeMux
85 r = r.WithContext(context.WithValue(r.Context(), addedTagsKey{}, &tags))
86 handler.ServeHTTP(w, r)
89 func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Request, func()) {
90 if isHealthEndpoint(r.URL.Path) {
94 if h.FormatSpanName == nil {
95 name = spanNameFromURL(r)
97 name = h.FormatSpanName(r)
101 startOpts := h.StartOptions
102 if h.GetStartOptions != nil {
103 startOpts = h.GetStartOptions(r)
107 sc, ok := h.extractSpanContext(r)
108 if ok && !h.IsPublicEndpoint {
109 ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc,
110 trace.WithSampler(startOpts.Sampler),
111 trace.WithSpanKind(trace.SpanKindServer))
113 ctx, span = trace.StartSpan(ctx, name,
114 trace.WithSampler(startOpts.Sampler),
115 trace.WithSpanKind(trace.SpanKindServer),
118 span.AddLink(trace.Link{
121 Type: trace.LinkTypeParent,
126 span.AddAttributes(requestAttrs(r)...)
127 return r.WithContext(ctx), span.End
130 func (h *Handler) extractSpanContext(r *http.Request) (trace.SpanContext, bool) {
131 if h.Propagation == nil {
132 return defaultFormat.SpanContextFromRequest(r)
134 return h.Propagation.SpanContextFromRequest(r)
137 func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func(tags *addedTags)) {
138 ctx, _ := tag.New(r.Context(),
139 tag.Upsert(Host, r.Host),
140 tag.Upsert(Path, r.URL.Path),
141 tag.Upsert(Method, r.Method))
142 track := &trackingResponseWriter{
148 // TODO: Handle cases where ContentLength is not set.
150 } else if r.ContentLength > 0 {
151 track.reqSize = r.ContentLength
153 stats.Record(ctx, ServerRequestCount.M(1))
154 return track.wrappedResponseWriter(), track.end
157 type trackingResponseWriter struct {
165 writer http.ResponseWriter
168 // Compile time assertion for ResponseWriter interface
169 var _ http.ResponseWriter = (*trackingResponseWriter)(nil)
171 var logTagsErrorOnce sync.Once
173 func (t *trackingResponseWriter) end(tags *addedTags) {
174 t.endOnce.Do(func() {
175 if t.statusCode == 0 {
179 span := trace.FromContext(t.ctx)
180 span.SetStatus(TraceStatus(t.statusCode, t.statusLine))
181 span.AddAttributes(trace.Int64Attribute(StatusCodeAttribute, int64(t.statusCode)))
183 m := []stats.Measurement{
184 ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
185 ServerResponseBytes.M(t.respSize),
188 m = append(m, ServerRequestBytes.M(t.reqSize))
190 allTags := make([]tag.Mutator, len(tags.t)+1)
191 allTags[0] = tag.Upsert(StatusCode, strconv.Itoa(t.statusCode))
192 copy(allTags[1:], tags.t)
193 stats.RecordWithTags(t.ctx, allTags, m...)
197 func (t *trackingResponseWriter) Header() http.Header {
198 return t.writer.Header()
201 func (t *trackingResponseWriter) Write(data []byte) (int, error) {
202 n, err := t.writer.Write(data)
203 t.respSize += int64(n)
207 func (t *trackingResponseWriter) WriteHeader(statusCode int) {
208 t.writer.WriteHeader(statusCode)
209 t.statusCode = statusCode
210 t.statusLine = http.StatusText(t.statusCode)
213 // wrappedResponseWriter returns a wrapped version of the original
214 // ResponseWriter and only implements the same combination of additional
215 // interfaces as the original.
216 // This implementation is based on https://github.com/felixge/httpsnoop.
217 func (t *trackingResponseWriter) wrappedResponseWriter() http.ResponseWriter {
219 hj, i0 = t.writer.(http.Hijacker)
220 cn, i1 = t.writer.(http.CloseNotifier)
221 pu, i2 = t.writer.(http.Pusher)
222 fl, i3 = t.writer.(http.Flusher)
223 rf, i4 = t.writer.(io.ReaderFrom)
227 case !i0 && !i1 && !i2 && !i3 && !i4:
231 case !i0 && !i1 && !i2 && !i3 && i4:
236 case !i0 && !i1 && !i2 && i3 && !i4:
241 case !i0 && !i1 && !i2 && i3 && i4:
247 case !i0 && !i1 && i2 && !i3 && !i4:
252 case !i0 && !i1 && i2 && !i3 && i4:
258 case !i0 && !i1 && i2 && i3 && !i4:
264 case !i0 && !i1 && i2 && i3 && i4:
271 case !i0 && i1 && !i2 && !i3 && !i4:
276 case !i0 && i1 && !i2 && !i3 && i4:
282 case !i0 && i1 && !i2 && i3 && !i4:
288 case !i0 && i1 && !i2 && i3 && i4:
295 case !i0 && i1 && i2 && !i3 && !i4:
301 case !i0 && i1 && i2 && !i3 && i4:
308 case !i0 && i1 && i2 && i3 && !i4:
315 case !i0 && i1 && i2 && i3 && i4:
323 case i0 && !i1 && !i2 && !i3 && !i4:
328 case i0 && !i1 && !i2 && !i3 && i4:
334 case i0 && !i1 && !i2 && i3 && !i4:
340 case i0 && !i1 && !i2 && i3 && i4:
347 case i0 && !i1 && i2 && !i3 && !i4:
353 case i0 && !i1 && i2 && !i3 && i4:
360 case i0 && !i1 && i2 && i3 && !i4:
367 case i0 && !i1 && i2 && i3 && i4:
375 case i0 && i1 && !i2 && !i3 && !i4:
381 case i0 && i1 && !i2 && !i3 && i4:
388 case i0 && i1 && !i2 && i3 && !i4:
395 case i0 && i1 && !i2 && i3 && i4:
403 case i0 && i1 && i2 && !i3 && !i4:
410 case i0 && i1 && i2 && !i3 && i4:
418 case i0 && i1 && i2 && i3 && !i4:
426 case i0 && i1 && i2 && i3 && i4:
434 }{t, hj, cn, pu, fl, rf}