-
Notifications
You must be signed in to change notification settings - Fork 38
/
config.go
179 lines (160 loc) · 7.06 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package otelchi
import (
"net/http"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/propagation"
oteltrace "go.opentelemetry.io/otel/trace"
)
// These defaults are used in `TraceHeaderConfig`.
const (
DefaultTraceIDResponseHeaderKey = "X-Trace-Id"
DefaultTraceSampledResponseHeaderKey = "X-Trace-Sampled"
)
// config is used to configure the mux middleware.
type config struct {
TracerProvider oteltrace.TracerProvider
Propagators propagation.TextMapPropagator
ChiRoutes chi.Routes
RequestMethodInSpanName bool
Filters []Filter
TraceIDResponseHeaderKey string
TraceSampledResponseHeaderKey string
PublicEndpointFn func(r *http.Request) bool
}
// Option specifies instrumentation configuration options.
type Option interface {
apply(*config)
}
type optionFunc func(*config)
func (o optionFunc) apply(c *config) {
o(c)
}
// 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
// WithPropagators specifies propagators to use for extracting
// information from the HTTP requests. If none are specified, global
// ones will be used.
func WithPropagators(propagators propagation.TextMapPropagator) Option {
return optionFunc(func(cfg *config) {
cfg.Propagators = propagators
})
}
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
// If none is specified, the global provider is used.
func WithTracerProvider(provider oteltrace.TracerProvider) Option {
return optionFunc(func(cfg *config) {
cfg.TracerProvider = provider
})
}
// WithChiRoutes specified the routes that being used by application. Its main
// purpose is to provide route pattern as span name during span creation. If this
// option is not set, by default the span will be given name at the end of span
// execution. For some people, this behavior is not desirable since they want
// to override the span name on underlying handler. By setting this option, it
// is possible for them to override the span name.
func WithChiRoutes(routes chi.Routes) Option {
return optionFunc(func(cfg *config) {
cfg.ChiRoutes = routes
})
}
// WithRequestMethodInSpanName is used for adding http request method to span name.
// While this is not necessary for vendors that properly implemented the tracing
// specs (e.g Jaeger, AWS X-Ray, etc...), but for other vendors such as Elastic
// and New Relic this might be helpful.
//
// See following threads for details:
//
// - https://github.com/riandyrn/otelchi/pull/3#issuecomment-1005883910
// - https://github.com/riandyrn/otelchi/issues/6#issuecomment-1034461912
func WithRequestMethodInSpanName(isActive bool) Option {
return optionFunc(func(cfg *config) {
cfg.RequestMethodInSpanName = isActive
})
}
// 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(filter Filter) Option {
return optionFunc(func(cfg *config) {
cfg.Filters = append(cfg.Filters, filter)
})
}
// WithTraceIDResponseHeader enables adding trace id into response header.
// It accepts a function that generates the header key name. If this parameter
// function set to `nil` the default header key which is `X-Trace-Id` will be used.
//
// Deprecated: use `WithTraceResponseHeaders` instead.
func WithTraceIDResponseHeader(headerKeyFunc func() string) Option {
cfg := TraceHeaderConfig{
TraceIDHeader: "",
TraceSampledHeader: "",
}
if headerKeyFunc != nil {
cfg.TraceIDHeader = headerKeyFunc()
}
return WithTraceResponseHeaders(cfg)
}
// TraceHeaderConfig is configuration for trace headers in the response.
type TraceHeaderConfig struct {
TraceIDHeader string // if non-empty overrides the default of X-Trace-ID
TraceSampledHeader string // if non-empty overrides the default of X-Trace-Sampled
}
// WithTraceResponseHeaders configures the response headers for trace information.
// It accepts a TraceHeaderConfig struct that contains the keys for the Trace ID
// and Trace Sampled headers. If the provided keys are empty, default values will
// be used for the respective headers.
func WithTraceResponseHeaders(cfg TraceHeaderConfig) Option {
return optionFunc(func(c *config) {
c.TraceIDResponseHeaderKey = cfg.TraceIDHeader
if c.TraceIDResponseHeaderKey == "" {
c.TraceIDResponseHeaderKey = DefaultTraceIDResponseHeaderKey
}
c.TraceSampledResponseHeaderKey = cfg.TraceSampledHeader
if c.TraceSampledResponseHeaderKey == "" {
c.TraceSampledResponseHeaderKey = DefaultTraceSampledResponseHeaderKey
}
})
}
// WithPublicEndpoint is used for marking every endpoint as public endpoint.
// This means if the incoming request has span context, it won't be used as
// parent span by the span generated by this middleware, instead the generated
// span will be the root span (new trace) and then linked to the span from the
// incoming request.
//
// Let say we have the following scenario:
//
// 1. We have 2 systems: `SysA` & `SysB`.
// 2. `SysA` has the following services: `SvcA.1` & `SvcA.2`.
// 3. `SysB` has the following services: `SvcB.1` & `SvcB.2`.
// 4. `SvcA.2` is used internally only by `SvcA.1`.
// 5. `SvcB.2` is used internally only by `SvcB.1`.
// 6. All of these services already instrumented otelchi & using the same collector (e.g Jaeger).
// 7. In `SvcA.1` we should set `WithPublicEndpoint()` since it is the entry point (a.k.a "public endpoint") for entering `SysA`.
// 8. In `SvcA.2` we should not set `WithPublicEndpoint()` since it is only used internally by `SvcA.1` inside `SysA`.
// 9. Point 7 & 8 also applies to both services in `SysB`.
//
// Now, whenever `SvcA.1` calls `SvcA.2` there will be only a single trace generated. This trace will contain 2 spans: root span from `SvcA.1` & child span from `SvcA.2`.
//
// But if let say `SvcA.2` calls `SvcB.1`, then there will be 2 traces generated: trace from `SysA` & trace from `SysB`. But in trace generated in `SysB` there will be like a marking that this trace is actually related to trace in `SysA` (a.k.a linked with the trace from `SysA`).
func WithPublicEndpoint() Option {
return WithPublicEndpointFn(func(r *http.Request) bool { return true })
}
// WithPublicEndpointFn runs with every request, and allows conditionally
// configuring the Handler to link the generated span with an incoming span
// context.
//
// If the function return `true` the generated span will be linked with the
// incoming span context. Otherwise, the generated span will be set as the
// child span of the incoming span context.
//
// Essentially it has the same functionality as `WithPublicEndpoint` but with
// more flexibility.
func WithPublicEndpointFn(fn func(r *http.Request) bool) Option {
return optionFunc(func(cfg *config) {
cfg.PublicEndpointFn = fn
})
}