Skip to content

Commit

Permalink
Merge pull request #91 from instana/instrument_net_http
Browse files Browse the repository at this point in the history
Instrument net/http
  • Loading branch information
Andrew Slotin authored Mar 25, 2020
2 parents 8727880 + a18454f commit 3209d8b
Show file tree
Hide file tree
Showing 12 changed files with 744 additions and 310 deletions.
28 changes: 9 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,11 @@ import (
ot "github.com/opentracing/opentracing-go"
)

// Doing registration and wrapping in two separate steps
// Doing registration and wrapping
func main() {
http.HandleFunc(
"/path/to/handler",
sensor.TracingHandler("myHandler", myHandler),
)
}

// Doing registration and wrapping in a single step
func main() {
http.HandleFunc(
sensor.TraceHandler("myHandler", "/path/to/handler", myHandler),
sensor.TracingHandlerFunc("myHandler", myHandler),
)
}

Expand All @@ -151,23 +144,20 @@ func myHandler(w http.ResponseWriter, req *http.Request) {

Requesting data or information from other, often external systems, is commonly implemented through HTTP requests. To make sure traces contain all spans, especially over all the different systems, certain span information have to be injected into the HTTP request headers before sending it out. Instana's Go sensor provides support to automate this process as much as possible.

To have Instana inject information into the request headers, create the `http.Request` as normal and wrap it with the Instana sensor function as in the following example.
To have Instana inject information into the request headers, create the `http.Client`, wrap its `Transport` with `instana.RoundTripper()` and use it as in the following example.

```go
req, err := http.NewRequest("GET", url, nil)
client := &http.Client{}
resp, err := sensor.TracingHttpRequest(
"myExternalCall",
parentSpan,
req,
client,
)
client := &http.Client{
Transport: instana.RoundTripper(sensor, nil),
}

ctx := instana.ContextWithSpan(context.Background(), parentSpan)
resp, err := client.Do(req.WithContext(ctx))
```

The provided `parentSpan` is the incoming request from the request handler (see above) and provides the necessary tracing and span information to create a child span and inject it into the request.

The request is, after injection, executing using the provided `http.Client` instance. Like the normal `(*http.Client).Do()` operation, the call will return a `http.Response` instance or an error proving information of the failure reason.

### GRPC servers and clients

[`github.com/instana/go-sensor/instrumentation/instagrpc`](./instrumentation/instagrpc) provides both unary and stream interceptors to instrument GRPC servers and clients that use `google.golang.org/grpc`.
Expand Down
72 changes: 13 additions & 59 deletions adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,63 +54,34 @@ func (s *Sensor) SetLogger(l LeveledLogger) {
// TraceHandler is similar to TracingHandler in regards, that it wraps an existing http.HandlerFunc
// into a named instance to support capturing tracing information and data. The returned values are
// compatible with handler registration methods, e.g. http.Handle()
//
// Deprecated: please use instana.TracingHandlerFunc() instead
func (s *Sensor) TraceHandler(name, pattern string, handler http.HandlerFunc) (string, http.HandlerFunc) {
return pattern, s.TracingHandler(name, handler)
}

// TracingHandler wraps an existing http.HandlerFunc into a named instance to support capturing tracing
// information and response data
//
// Deprecated: please use instana.TracingHandlerFunc() instead
func (s *Sensor) TracingHandler(name string, handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
s.WithTracingContext(name, w, req, func(span ot.Span, ctx context.Context) {
wrapped := &statusCodeRecorder{ResponseWriter: w}
handler.ServeHTTP(wrapped, req.WithContext(ctx))

if wrapped.Status > 0 {
span.SetTag(string(ext.HTTPStatusCode), wrapped.Status)
}
})
}
return TracingHandlerFunc(s, name, handler)
}

// TracingHttpRequest wraps an existing http.Request instance into a named instance to inject tracing and span
// header information into the actual HTTP wire transfer
//
// Deprecated: please use instana.RoundTripper() instead
func (s *Sensor) TracingHttpRequest(name string, parent, req *http.Request, client http.Client) (*http.Response, error) {
opts := []ot.StartSpanOption{
ext.SpanKindRPCClient,
ot.Tags{
string(ext.PeerHostname): req.Host,
string(ext.HTTPUrl): req.URL.String(),
string(ext.HTTPMethod): req.Method,
},
}

if parentSpan, ok := SpanFromContext(parent.Context()); ok {
opts = append(opts, ot.ChildOf(parentSpan.Context()))
}

span := s.tracer.StartSpan("client", opts...)
defer span.Finish()

headersCarrier := ot.HTTPHeadersCarrier(req.Header)
if err := s.tracer.Inject(span.Context(), ot.HTTPHeaders, headersCarrier); err != nil {
return nil, err
}

res, err := client.Do(req.WithContext(context.Background()))
if err != nil {
span.LogFields(otlog.Error(err))
return res, err
}

span.SetTag(string(ext.HTTPStatusCode), res.StatusCode)

return res, nil
client.Transport = RoundTripper(s, client.Transport)
return client.Do(req.WithContext(context.Background()))
}

// WithTracingSpan takes the given SpanSensitiveFunc and executes it under the scope of a child span, which is
// injected as an argument when calling the function. It uses the name of the caller as a span operation name
// unless a non-empty value is provided
//
// Deprecated: please use instana.TracingHandlerFunc() to instrument an HTTP handler
func (s *Sensor) WithTracingSpan(operationName string, w http.ResponseWriter, req *http.Request, f SpanSensitiveFunc) {
if operationName == "" {
pc, _, _, _ := runtime.Caller(1)
Expand Down Expand Up @@ -169,27 +140,10 @@ func (s *Sensor) WithTracingSpan(operationName string, w http.ResponseWriter, re

// Executes the given ContextSensitiveFunc and executes it under the scope of a newly created context.Context,
// that provides access to the parent span as 'parentSpan'.
//
// Deprecated: please use instana.TracingHandlerFunc() to instrument an HTTP handler
func (s *Sensor) WithTracingContext(name string, w http.ResponseWriter, req *http.Request, f ContextSensitiveFunc) {
s.WithTracingSpan(name, w, req, func(span ot.Span) {
f(span, ContextWithSpan(req.Context(), span))
})
}

// wrapper over http.ResponseWriter to spy the returned status code
type statusCodeRecorder struct {
http.ResponseWriter
Status int
}

func (rec *statusCodeRecorder) WriteHeader(status int) {
rec.Status = status
rec.ResponseWriter.WriteHeader(status)
}

func (rec *statusCodeRecorder) Write(b []byte) (int, error) {
if rec.Status == 0 {
rec.Status = http.StatusOK
}

return rec.ResponseWriter.Write(b)
}
2 changes: 1 addition & 1 deletion adapters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func TestTracingHttpRequest(t *testing.T) {

require.NotNil(t, span.Data)
require.NotNil(t, span.Data.SDK)
assert.Equal(t, "client", span.Data.SDK.Name)
assert.Equal(t, "net/http.Client", span.Data.SDK.Name)
assert.Equal(t, "exit", span.Data.SDK.Type)

require.NotNil(t, span.Data.SDK.Custom)
Expand Down
7 changes: 7 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Examples
========

For up-to-date instrumentation code examples please consult the respective godoc:

* [`github.com/instana/go-sensor`](https://pkg.go.dev/github.com/instana/go-sensor?tab=doc#pkg-overview) - HTTP client and server instrumentation
* [`github.com/instana/go-sensor/instrumentation/instagrpc`](https://pkg.go.dev/github.com/instana/go-sensor/instrumentation/instagrpc?tab=doc#pkg-overview) - GRPC server and client instrumentation
54 changes: 0 additions & 54 deletions example/httpclient/multi_request.go

This file was deleted.

85 changes: 0 additions & 85 deletions example/webserver/instana/http.go

This file was deleted.

Loading

0 comments on commit 3209d8b

Please sign in to comment.