@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 163 lines 5.32 KB
Raw
1 package tracing
2
3 import (
4 "context"
5 "fmt"
6 "net/http"
7
8 "go.opentelemetry.io/contrib/exporters/autoexport"
9 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
10 "go.opentelemetry.io/contrib/propagators/autoprop"
11 "go.opentelemetry.io/otel/attribute"
12 "go.opentelemetry.io/otel/propagation"
13 "go.opentelemetry.io/otel/sdk/resource"
14 sdktrace "go.opentelemetry.io/otel/sdk/trace"
15 semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
16 "go.opentelemetry.io/otel/trace"
17 "go.uber.org/zap"
18
19 "github.com/caddyserver/caddy/v2"
20 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
21 )
22
23 const (
24 defaultSpanName = "handler"
25 nextCallCtxKey caddy.CtxKey = "nextCall"
26 )
27
28 // nextCall store the next handler, and the error value return on calling it (if any)
29 type nextCall struct {
30 next caddyhttp.Handler
31 err error
32 }
33
34 // openTelemetryWrapper is responsible for the tracing injection, extraction and propagation.
35 type openTelemetryWrapper struct {
36 propagators propagation.TextMapPropagator
37
38 handler http.Handler
39
40 spanName string
41 spanAttributes map[string]string
42 }
43
44 // newOpenTelemetryWrapper is responsible for the openTelemetryWrapper initialization using provided configuration.
45 func newOpenTelemetryWrapper(
46 ctx context.Context,
47 spanName string,
48 spanAttributes map[string]string,
49 ) (openTelemetryWrapper, error) {
50 if spanName == "" {
51 spanName = defaultSpanName
52 }
53
54 ot := openTelemetryWrapper{
55 spanName: spanName,
56 spanAttributes: spanAttributes,
57 }
58
59 version, _ := caddy.Version()
60 res, err := ot.newResource(caddyhttp.ServerHeader, version)
61 if err != nil {
62 return ot, fmt.Errorf("creating resource error: %w", err)
63 }
64
65 ot.propagators = autoprop.NewTextMapPropagator()
66
67 // Defer creation of the exporter (and its batch span processor goroutine)
68 // until we know a new provider is actually needed. When the global provider
69 // already exists it is reused and these options are discarded; building them
70 // here unconditionally would leak the exporter and a BatchSpanProcessor
71 // goroutine on every config reload.
72 tracerProvider, err := globalTracerProvider.getTracerProvider(func() ([]sdktrace.TracerProviderOption, error) {
73 traceExporter, err := autoexport.NewSpanExporter(ctx)
74 if err != nil {
75 return nil, fmt.Errorf("creating trace exporter error: %w", err)
76 }
77
78 return []sdktrace.TracerProviderOption{
79 sdktrace.WithBatcher(traceExporter),
80 sdktrace.WithResource(res),
81 }, nil
82 })
83 if err != nil {
84 return ot, err
85 }
86
87 ot.handler = otelhttp.NewHandler(http.HandlerFunc(ot.serveHTTP),
88 ot.spanName,
89 otelhttp.WithTracerProvider(tracerProvider),
90 otelhttp.WithPropagators(ot.propagators),
91 otelhttp.WithSpanNameFormatter(ot.spanNameFormatter),
92 )
93
94 return ot, nil
95 }
96
97 // serveHTTP injects a tracing context and call the next handler.
98 func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request) {
99 ctx := r.Context()
100 ot.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
101 spanCtx := trace.SpanContextFromContext(ctx)
102 if spanCtx.IsValid() {
103 traceID := spanCtx.TraceID().String()
104 spanID := spanCtx.SpanID().String()
105 // Add a trace_id placeholder, accessible via `{http.vars.trace_id}`.
106 caddyhttp.SetVar(ctx, "trace_id", traceID)
107 // Add a span_id placeholder, accessible via `{http.vars.span_id}`.
108 caddyhttp.SetVar(ctx, "span_id", spanID)
109 // Add the traceID and spanID to the log fields for the request.
110 if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok {
111 extra.Add(zap.String("traceID", traceID))
112 extra.Add(zap.String("spanID", spanID))
113 }
114 }
115
116 next := ctx.Value(nextCallCtxKey).(*nextCall)
117 next.err = next.next.ServeHTTP(w, r)
118
119 // Add custom span attributes to the current span
120 span := trace.SpanFromContext(ctx)
121 if span.IsRecording() && len(ot.spanAttributes) > 0 {
122 replacer := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
123 attributes := make([]attribute.KeyValue, 0, len(ot.spanAttributes))
124 for key, value := range ot.spanAttributes {
125 // Allow placeholder replacement in attribute values
126 replacedValue := replacer.ReplaceAll(value, "")
127 attributes = append(attributes, attribute.String(key, replacedValue))
128 }
129 span.SetAttributes(attributes...)
130 }
131 }
132
133 // ServeHTTP propagates call to the by wrapped by `otelhttp` next handler.
134 func (ot *openTelemetryWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
135 n := &nextCall{
136 next: next,
137 err: nil,
138 }
139 ot.handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), nextCallCtxKey, n)))
140
141 return n.err
142 }
143
144 // cleanup flush all remaining data and shutdown a tracerProvider
145 func (ot *openTelemetryWrapper) cleanup(logger *zap.Logger) error {
146 return globalTracerProvider.cleanupTracerProvider(logger)
147 }
148
149 // newResource creates a resource that describe current handler instance and merge it with a default attributes value.
150 func (ot *openTelemetryWrapper) newResource(
151 webEngineName,
152 webEngineVersion string,
153 ) (*resource.Resource, error) {
154 return resource.Merge(resource.Default(), resource.NewSchemaless(
155 semconv.WebEngineName(webEngineName),
156 semconv.WebEngineVersion(webEngineVersion),
157 ))
158 }
159
160 // spanNameFormatter performs the replacement of placeholders in the span name
161 func (ot *openTelemetryWrapper) spanNameFormatter(operation string, r *http.Request) string {
162 return r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer).ReplaceAll(operation, "")
163 }