Skip to content

Commit 3fc65dc

Browse files
Aneurysm9MrAlias
andauthored
Add instrumentation for net/http and net/httptrace (#190)
* Move othttp instrumentation to contrib repo * api/standard package has moved to semconv * Replace othttp references with otelhttp * Revert "api/standard package has moved to semconv" This reverts commit eceaa35 as the change has not yet been published in a versioned module. Leaving the revert commit in history for ease of resurrection. * reference correct contrib module version * Add net/http/httptrace instrumentation * add httptrace module to dependabot config * fix precommit issues * Add net/http example * add httptrace example * Restore response writer wrapper from opentelemery-go#979 * Add CHANGELOG entry Co-authored-by: Tyler Yahn <[email protected]>
1 parent b469fe2 commit 3fc65dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3834
-2
lines changed

.github/dependabot.yml

+8
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,11 @@ updates:
6565
directory: "/instrumentation/github.com/Shopify/sarama" # Location of package manifests
6666
schedule:
6767
interval: "daily"
68+
- package-ecosystem: "gomod" # See documentation for possible values
69+
directory: "/instrumentation/net/http" # Location of package manifests
70+
schedule:
71+
interval: "daily"
72+
- package-ecosystem: "gomod" # See documentation for possible values
73+
directory: "/instrumentation/net/http/httptrace" # Location of package manifests
74+
schedule:
75+
interval: "daily"

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ Thumbs.db
88
*.so
99
coverage.*
1010

11+
instrumentation/github.com/gocql/gocql/example/example
1112
instrumentation/google.golang.org/grpc/example/server/server
12-
instrumentation/google.golang.org/grpc/example/client/client
13+
instrumentation/google.golang.org/grpc/example/client/client

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1111
### Added
1212

1313
- The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc` module has been added to replace the instrumentation that had previoiusly existed in the `go.opentelemetry.io/otel/instrumentation/grpctrace` package. (#189)
14+
- Instrumentation for the stdlib `net/http` and `net/http/httptrace` packages. (#190)
1415

1516
## [0.10.0] - 2020-07-31
1617

instrumentation/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Instrumentation
22

3-
Code contained in this directory contains instrumentation for 3rd-party Go packages.
3+
Code contained in this directory contains instrumentation for 3rd-party Go packages and some packages from the standard library.
44

55
## Organization
66

instrumentation/net/http/common.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright The 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 http
16+
17+
import (
18+
"net/http"
19+
20+
"go.opentelemetry.io/otel/api/kv"
21+
)
22+
23+
// Attribute keys that can be added to a span.
24+
const (
25+
ReadBytesKey = kv.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
26+
ReadErrorKey = kv.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
27+
WroteBytesKey = kv.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
28+
WriteErrorKey = kv.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
29+
)
30+
31+
// Server HTTP metrics
32+
const (
33+
RequestCount = "http.server.request_count" // Incoming request count total
34+
RequestContentLength = "http.server.request_content_length" // Incoming request bytes total
35+
ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total
36+
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds
37+
)
38+
39+
// Filter is a predicate used to determine whether a given http.request should
40+
// be traced. A Filter must return true if the request should be traced.
41+
type Filter func(*http.Request) bool

instrumentation/net/http/config.go

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright The 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 http
16+
17+
import (
18+
"net/http"
19+
20+
"go.opentelemetry.io/otel/api/metric"
21+
"go.opentelemetry.io/otel/api/propagation"
22+
"go.opentelemetry.io/otel/api/trace"
23+
)
24+
25+
// Config represents the configuration options available for the http.Handler
26+
// and http.Transport types.
27+
type Config struct {
28+
Tracer trace.Tracer
29+
Meter metric.Meter
30+
Propagators propagation.Propagators
31+
SpanStartOptions []trace.StartOption
32+
ReadEvent bool
33+
WriteEvent bool
34+
Filters []Filter
35+
SpanNameFormatter func(string, *http.Request) string
36+
}
37+
38+
// Option Interface used for setting *optional* Config properties
39+
type Option interface {
40+
Apply(*Config)
41+
}
42+
43+
// OptionFunc provides a convenience wrapper for simple Options
44+
// that can be represented as functions.
45+
type OptionFunc func(*Config)
46+
47+
func (o OptionFunc) Apply(c *Config) {
48+
o(c)
49+
}
50+
51+
// NewConfig creates a new Config struct and applies opts to it.
52+
func NewConfig(opts ...Option) *Config {
53+
c := &Config{}
54+
for _, opt := range opts {
55+
opt.Apply(c)
56+
}
57+
return c
58+
}
59+
60+
// WithTracer configures a specific tracer. If this option
61+
// isn't specified then the global tracer is used.
62+
func WithTracer(tracer trace.Tracer) Option {
63+
return OptionFunc(func(c *Config) {
64+
c.Tracer = tracer
65+
})
66+
}
67+
68+
// WithMeter configures a specific meter. If this option
69+
// isn't specified then the global meter is used.
70+
func WithMeter(meter metric.Meter) Option {
71+
return OptionFunc(func(c *Config) {
72+
c.Meter = meter
73+
})
74+
}
75+
76+
// WithPublicEndpoint configures the Handler to link the span with an incoming
77+
// span context. If this option is not provided, then the association is a child
78+
// association instead of a link.
79+
func WithPublicEndpoint() Option {
80+
return OptionFunc(func(c *Config) {
81+
c.SpanStartOptions = append(c.SpanStartOptions, trace.WithNewRoot())
82+
})
83+
}
84+
85+
// WithPropagators configures specific propagators. If this
86+
// option isn't specified then
87+
// go.opentelemetry.io/otel/api/global.Propagators are used.
88+
func WithPropagators(ps propagation.Propagators) Option {
89+
return OptionFunc(func(c *Config) {
90+
c.Propagators = ps
91+
})
92+
}
93+
94+
// WithSpanOptions configures an additional set of
95+
// trace.StartOptions, which are applied to each new span.
96+
func WithSpanOptions(opts ...trace.StartOption) Option {
97+
return OptionFunc(func(c *Config) {
98+
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
99+
})
100+
}
101+
102+
// WithFilter adds a filter to the list of filters used by the handler.
103+
// If any filter indicates to exclude a request then the request will not be
104+
// traced. All filters must allow a request to be traced for a Span to be created.
105+
// If no filters are provided then all requests are traced.
106+
// Filters will be invoked for each processed request, it is advised to make them
107+
// simple and fast.
108+
func WithFilter(f Filter) Option {
109+
return OptionFunc(func(c *Config) {
110+
c.Filters = append(c.Filters, f)
111+
})
112+
}
113+
114+
type event int
115+
116+
// Different types of events that can be recorded, see WithMessageEvents
117+
const (
118+
ReadEvents event = iota
119+
WriteEvents
120+
)
121+
122+
// WithMessageEvents configures the Handler to record the specified events
123+
// (span.AddEvent) on spans. By default only summary attributes are added at the
124+
// end of the request.
125+
//
126+
// Valid events are:
127+
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read
128+
// using the ReadBytesKey
129+
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
130+
// using the WriteBytesKey
131+
func WithMessageEvents(events ...event) Option {
132+
return OptionFunc(func(c *Config) {
133+
for _, e := range events {
134+
switch e {
135+
case ReadEvents:
136+
c.ReadEvent = true
137+
case WriteEvents:
138+
c.WriteEvent = true
139+
}
140+
}
141+
})
142+
}
143+
144+
// WithSpanNameFormatter takes a function that will be called on every
145+
// request and the returned string will become the Span Name
146+
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
147+
return OptionFunc(func(c *Config) {
148+
c.SpanNameFormatter = f
149+
})
150+
}
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright The 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 http
16+
17+
import (
18+
"io"
19+
"io/ioutil"
20+
"net/http"
21+
"net/http/httptest"
22+
"testing"
23+
24+
mocktrace "go.opentelemetry.io/contrib/internal/trace"
25+
)
26+
27+
func TestBasicFilter(t *testing.T) {
28+
rr := httptest.NewRecorder()
29+
30+
tracer := mocktrace.Tracer{}
31+
32+
h := NewHandler(
33+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
34+
if _, err := io.WriteString(w, "hello world"); err != nil {
35+
t.Fatal(err)
36+
}
37+
}), "test_handler",
38+
WithTracer(&tracer),
39+
WithFilter(func(r *http.Request) bool {
40+
return false
41+
}),
42+
)
43+
44+
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
h.ServeHTTP(rr, r)
49+
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
50+
t.Fatalf("got %d, expected %d", got, expected)
51+
}
52+
if got := rr.Header().Get("Traceparent"); got != "" {
53+
t.Fatal("expected empty trace header")
54+
}
55+
if got, expected := tracer.StartSpanID, uint64(0); got != expected {
56+
t.Fatalf("got %d, expected %d", got, expected)
57+
}
58+
d, err := ioutil.ReadAll(rr.Result().Body)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
if got, expected := string(d), "hello world"; got != expected {
63+
t.Fatalf("got %q, expected %q", got, expected)
64+
}
65+
}
66+
67+
func TestSpanNameFormatter(t *testing.T) {
68+
var testCases = []struct {
69+
name string
70+
formatter func(s string, r *http.Request) string
71+
operation string
72+
expected string
73+
}{
74+
{
75+
name: "default handler formatter",
76+
formatter: defaultHandlerFormatter,
77+
operation: "test_operation",
78+
expected: "test_operation",
79+
},
80+
{
81+
name: "default transport formatter",
82+
formatter: defaultTransportFormatter,
83+
expected: http.MethodGet,
84+
},
85+
{
86+
name: "custom formatter",
87+
formatter: func(s string, r *http.Request) string {
88+
return r.URL.Path
89+
},
90+
operation: "",
91+
expected: "/hello",
92+
},
93+
}
94+
95+
for _, tc := range testCases {
96+
t.Run(tc.name, func(t *testing.T) {
97+
rr := httptest.NewRecorder()
98+
var spanName string
99+
tracer := mocktrace.Tracer{
100+
OnSpanStarted: func(span *mocktrace.Span) {
101+
spanName = span.Name
102+
},
103+
}
104+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105+
if _, err := io.WriteString(w, "hello world"); err != nil {
106+
t.Fatal(err)
107+
}
108+
})
109+
h := NewHandler(
110+
handler,
111+
tc.operation,
112+
WithTracer(&tracer),
113+
WithSpanNameFormatter(tc.formatter),
114+
)
115+
r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", nil)
116+
if err != nil {
117+
t.Fatal(err)
118+
}
119+
h.ServeHTTP(rr, r)
120+
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
121+
t.Fatalf("got %d, expected %d", got, expected)
122+
}
123+
if got, expected := spanName, tc.expected; got != expected {
124+
t.Fatalf("got %q, expected %q", got, expected)
125+
}
126+
})
127+
}
128+
}

instrumentation/net/http/doc.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright The 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 http 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 http

0 commit comments

Comments
 (0)