Skip to content

Commit 4172bdf

Browse files
freeformzrghetia
authored andcommitted
Fix up docs and other othttp cleanups (#218)
1 parent 3a9c80c commit 4172bdf

File tree

6 files changed

+272
-310
lines changed

6 files changed

+272
-310
lines changed

plugin/othttp/doc.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
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
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
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.
14+
15+
// Package othttp provides a http.Handler and functions that are
16+
// intended to be used to add tracing by wrapping
17+
// existing handlers (with Handler) and routes WithRouteTag.
18+
package othttp

plugin/othttp/handler.go

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
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
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
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.
14+
15+
package othttp
16+
17+
import (
18+
"io"
19+
"net/http"
20+
21+
"go.opentelemetry.io/api/core"
22+
"go.opentelemetry.io/api/propagation"
23+
"go.opentelemetry.io/api/trace"
24+
prop "go.opentelemetry.io/propagation"
25+
)
26+
27+
var _ http.Handler = &Handler{}
28+
29+
// Attribute keys that the Handler can add to a span.
30+
const (
31+
HostKey = core.Key("http.host") // the http host (http.Request.Host)
32+
MethodKey = core.Key("http.method") // the http method (http.Request.Method)
33+
PathKey = core.Key("http.path") // the http path (http.Request.URL.Path)
34+
URLKey = core.Key("http.url") // the http url (http.Request.URL.String())
35+
UserAgentKey = core.Key("http.user_agent") // the http user agent (http.Request.UserAgent())
36+
RouteKey = core.Key("http.route") // the http route (ex: /users/:id)
37+
StatusCodeKey = core.Key("http.status_code") // if set, the http status
38+
ReadBytesKey = core.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
39+
ReadErrorKey = core.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
40+
WroteBytesKey = core.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
41+
WriteErrorKey = core.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
42+
)
43+
44+
// Handler is http middleware that corresponds to the http.Handler interface and
45+
// is designed to wrap a http.Mux (or equivalent), while individual routes on
46+
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
47+
// to the span using the core.Keys defined in this package.
48+
type Handler struct {
49+
operation string
50+
handler http.Handler
51+
52+
tracer trace.Tracer
53+
prop propagation.TextFormatPropagator
54+
spanOptions []trace.SpanOption
55+
public bool
56+
readEvent bool
57+
writeEvent bool
58+
}
59+
60+
// Option function used for setting *optional* Handler properties
61+
type Option func(*Handler)
62+
63+
// WithTracer configures the Handler with a specific tracer. If this option
64+
// isn't specified then the global tracer is used.
65+
func WithTracer(tracer trace.Tracer) Option {
66+
return func(h *Handler) {
67+
h.tracer = tracer
68+
}
69+
}
70+
71+
// WithPublicEndpoint configures the Handler to link the span with an incoming
72+
// span context. If this option is not provided, then the association is a child
73+
// association instead of a link.
74+
func WithPublicEndpoint() Option {
75+
return func(h *Handler) {
76+
h.public = true
77+
}
78+
}
79+
80+
// WithPropagator configures the Handler with a specific propagator. If this
81+
// option isn't specificed then
82+
// go.opentelemetry.io/propagation.HTTPTraceContextPropagator is used.
83+
func WithPropagator(p propagation.TextFormatPropagator) Option {
84+
return func(h *Handler) {
85+
h.prop = p
86+
}
87+
}
88+
89+
// WithSpanOptions configures the Handler with an additional set of
90+
// trace.SpanOptions, which are applied to each new span.
91+
func WithSpanOptions(opts ...trace.SpanOption) Option {
92+
return func(h *Handler) {
93+
h.spanOptions = opts
94+
}
95+
}
96+
97+
type event int
98+
99+
// Different types of events that can be recorded, see WithMessageEvents
100+
const (
101+
ReadEvents event = iota
102+
WriteEvents
103+
)
104+
105+
// WithMessageEvents configures the Handler to record the specified events
106+
// (span.AddEvent) on spans. By default only summary attributes are added at the
107+
// end of the request.
108+
//
109+
// Valid events are:
110+
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read
111+
// using the ReadBytesKey
112+
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
113+
// using the WriteBytesKey
114+
func WithMessageEvents(events ...event) Option {
115+
return func(h *Handler) {
116+
for _, e := range events {
117+
switch e {
118+
case ReadEvents:
119+
h.readEvent = true
120+
case WriteEvents:
121+
h.writeEvent = true
122+
}
123+
}
124+
}
125+
}
126+
127+
// NewHandler wraps the passed handler, functioning like middleware, in a span
128+
// named after the operation and with any provided HandlerOptions.
129+
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
130+
h := Handler{handler: handler, operation: operation}
131+
defaultOpts := []Option{
132+
WithTracer(trace.GlobalTracer()),
133+
WithPropagator(prop.HTTPTraceContextPropagator{}),
134+
}
135+
136+
for _, opt := range append(defaultOpts, opts...) {
137+
opt(&h)
138+
}
139+
return &h
140+
}
141+
142+
// ServeHTTP serves HTTP requests (http.Handler)
143+
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
144+
opts := append([]trace.SpanOption{}, h.spanOptions...) // start with the configured options
145+
146+
sc := h.prop.Extract(r.Context(), r.Header)
147+
if sc.IsValid() { // not a valid span context, so no link / parent relationship to establish
148+
var opt trace.SpanOption
149+
if h.public {
150+
// TODO: If the endpoint is a public endpoint, it should start a new trace
151+
// and incoming remote sctx should be added as a link
152+
// (WithLinks(links...), this option doesn't exist yet). Replace ChildOf
153+
// below with something like: opt = trace.WithLinks(sc)
154+
opt = trace.ChildOf(sc)
155+
} else { // not a private endpoint, so assume child relationship
156+
opt = trace.ChildOf(sc)
157+
}
158+
opts = append(opts, opt)
159+
}
160+
161+
ctx, span := h.tracer.Start(r.Context(), h.operation, opts...)
162+
defer span.End()
163+
164+
readRecordFunc := func(int64) {}
165+
if h.readEvent {
166+
readRecordFunc = func(n int64) {
167+
span.AddEvent(ctx, "read", ReadBytesKey.Int64(n))
168+
}
169+
}
170+
bw := bodyWrapper{ReadCloser: r.Body, record: readRecordFunc}
171+
r.Body = &bw
172+
173+
writeRecordFunc := func(int64) {}
174+
if h.writeEvent {
175+
writeRecordFunc = func(n int64) {
176+
span.AddEvent(ctx, "write", WroteBytesKey.Int64(n))
177+
}
178+
}
179+
180+
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, injector: h.prop}
181+
182+
// Setup basic span attributes before calling handler.ServeHTTP so that they
183+
// are available to be mutated by the handler if needed.
184+
span.SetAttributes(
185+
HostKey.String(r.Host),
186+
MethodKey.String(r.Method),
187+
PathKey.String(r.URL.Path),
188+
URLKey.String(r.URL.String()),
189+
UserAgentKey.String(r.UserAgent()),
190+
)
191+
192+
h.handler.ServeHTTP(rww, r.WithContext(ctx))
193+
194+
setAfterServeAttributes(span, bw.read, rww.written, int64(rww.statusCode), bw.err, rww.err)
195+
}
196+
197+
func setAfterServeAttributes(span trace.Span, read, wrote, statusCode int64, rerr, werr error) {
198+
kv := make([]core.KeyValue, 0, 5)
199+
// TODO: Consider adding an event after each read and write, possibly as an
200+
// option (defaulting to off), so as to not create needlessly verbose spans.
201+
if read > 0 {
202+
kv = append(kv, ReadBytesKey.Int64(read))
203+
}
204+
if rerr != nil && rerr != io.EOF {
205+
kv = append(kv, ReadErrorKey.String(rerr.Error()))
206+
}
207+
if wrote > 0 {
208+
kv = append(kv, WroteBytesKey.Int64(wrote))
209+
}
210+
if statusCode > 0 {
211+
kv = append(kv, StatusCodeKey.Int64(statusCode))
212+
}
213+
if werr != nil && werr != io.EOF {
214+
kv = append(kv, WriteErrorKey.String(werr.Error()))
215+
}
216+
span.SetAttributes(kv...)
217+
}
218+
219+
// WithRouteTag annotates a span with the provided route name using the
220+
// RouteKey Tag.
221+
func WithRouteTag(route string, h http.Handler) http.Handler {
222+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
223+
span := trace.CurrentSpan(r.Context())
224+
//TODO: Why doesn't tag.Upsert work?
225+
span.SetAttribute(RouteKey.String(route))
226+
h.ServeHTTP(w, r.WithContext(trace.SetCurrentSpan(r.Context(), span)))
227+
})
228+
}

plugin/othttp/server_example_test.go renamed to plugin/othttp/handler_example_test.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,7 @@ func ExampleNewHandler() {
6868
case "":
6969
err = fmt.Errorf("expected /hello/:name in %q", s)
7070
default:
71-
span := trace.CurrentSpan(ctx)
72-
span.SetAttribute(
73-
core.KeyValue{Key: "name",
74-
Value: core.Value{Type: core.STRING, String: pp[1]},
75-
},
76-
)
71+
trace.CurrentSpan(ctx).SetAttribute(core.Key("name").String(pp[1]))
7772
}
7873
return pp[1], err
7974
}
@@ -113,7 +108,7 @@ func ExampleNewHandler() {
113108

114109
if err := http.ListenAndServe(":7777",
115110
othttp.NewHandler(&mux, "server",
116-
othttp.WithMessageEvents(othttp.EventRead, othttp.EventWrite),
111+
othttp.WithMessageEvents(othttp.ReadEvents, othttp.WriteEvents),
117112
),
118113
); err != nil {
119114
log.Fatal(err)
File renamed without changes.

0 commit comments

Comments
 (0)