-
Notifications
You must be signed in to change notification settings - Fork 576
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <MrAlias@users.noreply.github.com>
- Loading branch information
Showing
47 changed files
with
3,834 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright The OpenTelemetry 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 http | ||
|
||
import ( | ||
"net/http" | ||
|
||
"go.opentelemetry.io/otel/api/kv" | ||
) | ||
|
||
// Attribute keys that can be added to a span. | ||
const ( | ||
ReadBytesKey = kv.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read | ||
ReadErrorKey = kv.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded) | ||
WroteBytesKey = kv.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written | ||
WriteErrorKey = kv.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) | ||
) | ||
|
||
// Server HTTP metrics | ||
const ( | ||
RequestCount = "http.server.request_count" // Incoming request count total | ||
RequestContentLength = "http.server.request_content_length" // Incoming request bytes total | ||
ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total | ||
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds | ||
) | ||
|
||
// Filter is a predicate used to determine whether a given http.request should | ||
// be traced. A Filter must return true if the request should be traced. | ||
type Filter func(*http.Request) bool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright The OpenTelemetry 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 http | ||
|
||
import ( | ||
"net/http" | ||
|
||
"go.opentelemetry.io/otel/api/metric" | ||
"go.opentelemetry.io/otel/api/propagation" | ||
"go.opentelemetry.io/otel/api/trace" | ||
) | ||
|
||
// Config represents the configuration options available for the http.Handler | ||
// and http.Transport types. | ||
type Config struct { | ||
Tracer trace.Tracer | ||
Meter metric.Meter | ||
Propagators propagation.Propagators | ||
SpanStartOptions []trace.StartOption | ||
ReadEvent bool | ||
WriteEvent bool | ||
Filters []Filter | ||
SpanNameFormatter func(string, *http.Request) string | ||
} | ||
|
||
// Option Interface used for setting *optional* Config properties | ||
type Option interface { | ||
Apply(*Config) | ||
} | ||
|
||
// OptionFunc provides a convenience wrapper for simple Options | ||
// that can be represented as functions. | ||
type OptionFunc func(*Config) | ||
|
||
func (o OptionFunc) Apply(c *Config) { | ||
o(c) | ||
} | ||
|
||
// NewConfig creates a new Config struct and applies opts to it. | ||
func NewConfig(opts ...Option) *Config { | ||
c := &Config{} | ||
for _, opt := range opts { | ||
opt.Apply(c) | ||
} | ||
return c | ||
} | ||
|
||
// WithTracer configures a specific tracer. If this option | ||
// isn't specified then the global tracer is used. | ||
func WithTracer(tracer trace.Tracer) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.Tracer = tracer | ||
}) | ||
} | ||
|
||
// WithMeter configures a specific meter. If this option | ||
// isn't specified then the global meter is used. | ||
func WithMeter(meter metric.Meter) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.Meter = meter | ||
}) | ||
} | ||
|
||
// WithPublicEndpoint configures the Handler to link the span with an incoming | ||
// span context. If this option is not provided, then the association is a child | ||
// association instead of a link. | ||
func WithPublicEndpoint() Option { | ||
return OptionFunc(func(c *Config) { | ||
c.SpanStartOptions = append(c.SpanStartOptions, trace.WithNewRoot()) | ||
}) | ||
} | ||
|
||
// WithPropagators configures specific propagators. If this | ||
// option isn't specified then | ||
// go.opentelemetry.io/otel/api/global.Propagators are used. | ||
func WithPropagators(ps propagation.Propagators) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.Propagators = ps | ||
}) | ||
} | ||
|
||
// WithSpanOptions configures an additional set of | ||
// trace.StartOptions, which are applied to each new span. | ||
func WithSpanOptions(opts ...trace.StartOption) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.SpanStartOptions = append(c.SpanStartOptions, opts...) | ||
}) | ||
} | ||
|
||
// WithFilter adds a filter to the list of filters used by the handler. | ||
// If any filter indicates to exclude a request then the request will not be | ||
// traced. All filters must allow a request to be traced for a Span to be created. | ||
// If no filters are provided then all requests are traced. | ||
// Filters will be invoked for each processed request, it is advised to make them | ||
// simple and fast. | ||
func WithFilter(f Filter) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.Filters = append(c.Filters, f) | ||
}) | ||
} | ||
|
||
type event int | ||
|
||
// Different types of events that can be recorded, see WithMessageEvents | ||
const ( | ||
ReadEvents event = iota | ||
WriteEvents | ||
) | ||
|
||
// WithMessageEvents configures the Handler to record the specified events | ||
// (span.AddEvent) on spans. By default only summary attributes are added at the | ||
// end of the request. | ||
// | ||
// Valid events are: | ||
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read | ||
// using the ReadBytesKey | ||
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write | ||
// using the WriteBytesKey | ||
func WithMessageEvents(events ...event) Option { | ||
return OptionFunc(func(c *Config) { | ||
for _, e := range events { | ||
switch e { | ||
case ReadEvents: | ||
c.ReadEvent = true | ||
case WriteEvents: | ||
c.WriteEvent = true | ||
} | ||
} | ||
}) | ||
} | ||
|
||
// WithSpanNameFormatter takes a function that will be called on every | ||
// request and the returned string will become the Span Name | ||
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option { | ||
return OptionFunc(func(c *Config) { | ||
c.SpanNameFormatter = f | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright The OpenTelemetry 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 http | ||
|
||
import ( | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
mocktrace "go.opentelemetry.io/contrib/internal/trace" | ||
) | ||
|
||
func TestBasicFilter(t *testing.T) { | ||
rr := httptest.NewRecorder() | ||
|
||
tracer := mocktrace.Tracer{} | ||
|
||
h := NewHandler( | ||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if _, err := io.WriteString(w, "hello world"); err != nil { | ||
t.Fatal(err) | ||
} | ||
}), "test_handler", | ||
WithTracer(&tracer), | ||
WithFilter(func(r *http.Request) bool { | ||
return false | ||
}), | ||
) | ||
|
||
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
h.ServeHTTP(rr, r) | ||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { | ||
t.Fatalf("got %d, expected %d", got, expected) | ||
} | ||
if got := rr.Header().Get("Traceparent"); got != "" { | ||
t.Fatal("expected empty trace header") | ||
} | ||
if got, expected := tracer.StartSpanID, uint64(0); got != expected { | ||
t.Fatalf("got %d, expected %d", got, expected) | ||
} | ||
d, err := ioutil.ReadAll(rr.Result().Body) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if got, expected := string(d), "hello world"; got != expected { | ||
t.Fatalf("got %q, expected %q", got, expected) | ||
} | ||
} | ||
|
||
func TestSpanNameFormatter(t *testing.T) { | ||
var testCases = []struct { | ||
name string | ||
formatter func(s string, r *http.Request) string | ||
operation string | ||
expected string | ||
}{ | ||
{ | ||
name: "default handler formatter", | ||
formatter: defaultHandlerFormatter, | ||
operation: "test_operation", | ||
expected: "test_operation", | ||
}, | ||
{ | ||
name: "default transport formatter", | ||
formatter: defaultTransportFormatter, | ||
expected: http.MethodGet, | ||
}, | ||
{ | ||
name: "custom formatter", | ||
formatter: func(s string, r *http.Request) string { | ||
return r.URL.Path | ||
}, | ||
operation: "", | ||
expected: "/hello", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
rr := httptest.NewRecorder() | ||
var spanName string | ||
tracer := mocktrace.Tracer{ | ||
OnSpanStarted: func(span *mocktrace.Span) { | ||
spanName = span.Name | ||
}, | ||
} | ||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if _, err := io.WriteString(w, "hello world"); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
h := NewHandler( | ||
handler, | ||
tc.operation, | ||
WithTracer(&tracer), | ||
WithSpanNameFormatter(tc.formatter), | ||
) | ||
r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
h.ServeHTTP(rr, r) | ||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { | ||
t.Fatalf("got %d, expected %d", got, expected) | ||
} | ||
if got, expected := spanName, tc.expected; got != expected { | ||
t.Fatalf("got %q, expected %q", got, expected) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright The OpenTelemetry 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 http provides a http.Handler and functions that are | ||
// intended to be used to add tracing by wrapping | ||
// existing handlers (with Handler) and routes WithRouteTag. | ||
package http |
Oops, something went wrong.