You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
10 KiB
430 lines
10 KiB
// 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
|
|
|
|
// 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()
|
|
var span *trace.Span
|
|
sc, ok := h.extractSpanContext(r)
|
|
if ok && !h.IsPublicEndpoint {
|
|
ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc,
|
|
trace.WithSampler(h.StartOptions.Sampler),
|
|
trace.WithSpanKind(trace.SpanKindServer))
|
|
} else {
|
|
ctx, span = trace.StartSpan(ctx, name,
|
|
trace.WithSampler(h.StartOptions.Sampler),
|
|
trace.WithSpanKind(trace.SpanKindServer),
|
|
)
|
|
if ok {
|
|
span.AddLink(trace.Link{
|
|
TraceID: sc.TraceID,
|
|
SpanID: sc.SpanID,
|
|
Type: trace.LinkTypeChild,
|
|
Attributes: nil,
|
|
})
|
|
}
|
|
}
|
|
span.AddAttributes(requestAttrs(r)...)
|
|
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.URL.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)
|
|
|
|
var logTagsErrorOnce sync.Once
|
|
|
|
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))
|
|
|
|
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)
|
|
ctx, _ := tag.New(t.ctx, allTags...)
|
|
stats.Record(ctx, 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)
|
|
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}
|
|
}
|
|
}
|
|
|