blob: aa7b262d0d97a625948f8db9dbc2f52024c2f312 [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4package trace // import "go.opentelemetry.io/otel/sdk/trace"
5
6import (
7 "context"
8 "encoding/binary"
9 "fmt"
10
11 "go.opentelemetry.io/otel/attribute"
12 "go.opentelemetry.io/otel/trace"
13)
14
15// Sampler decides whether a trace should be sampled and exported.
16type Sampler interface {
17 // DO NOT CHANGE: any modification will not be backwards compatible and
18 // must never be done outside of a new major release.
19
20 // ShouldSample returns a SamplingResult based on a decision made from the
21 // passed parameters.
22 ShouldSample(parameters SamplingParameters) SamplingResult
23 // DO NOT CHANGE: any modification will not be backwards compatible and
24 // must never be done outside of a new major release.
25
26 // Description returns information describing the Sampler.
27 Description() string
28 // DO NOT CHANGE: any modification will not be backwards compatible and
29 // must never be done outside of a new major release.
30}
31
32// SamplingParameters contains the values passed to a Sampler.
33type SamplingParameters struct {
34 ParentContext context.Context
35 TraceID trace.TraceID
36 Name string
37 Kind trace.SpanKind
38 Attributes []attribute.KeyValue
39 Links []trace.Link
40}
41
42// SamplingDecision indicates whether a span is dropped, recorded and/or sampled.
43type SamplingDecision uint8
44
45// Valid sampling decisions.
46const (
47 // Drop will not record the span and all attributes/events will be dropped.
48 Drop SamplingDecision = iota
49
50 // RecordOnly indicates the span's IsRecording method returns true, but trace.FlagsSampled flag
51 // must not be set.
52 RecordOnly
53
54 // RecordAndSample indicates the span's IsRecording method returns true and trace.FlagsSampled flag
55 // must be set.
56 RecordAndSample
57)
58
59// SamplingResult conveys a SamplingDecision, set of Attributes and a Tracestate.
60type SamplingResult struct {
61 Decision SamplingDecision
62 Attributes []attribute.KeyValue
63 Tracestate trace.TraceState
64}
65
66type traceIDRatioSampler struct {
67 traceIDUpperBound uint64
68 description string
69}
70
71func (ts traceIDRatioSampler) ShouldSample(p SamplingParameters) SamplingResult {
72 psc := trace.SpanContextFromContext(p.ParentContext)
73 x := binary.BigEndian.Uint64(p.TraceID[8:16]) >> 1
74 if x < ts.traceIDUpperBound {
75 return SamplingResult{
76 Decision: RecordAndSample,
77 Tracestate: psc.TraceState(),
78 }
79 }
80 return SamplingResult{
81 Decision: Drop,
82 Tracestate: psc.TraceState(),
83 }
84}
85
86func (ts traceIDRatioSampler) Description() string {
87 return ts.description
88}
89
90// TraceIDRatioBased samples a given fraction of traces. Fractions >= 1 will
91// always sample. Fractions < 0 are treated as zero. To respect the
92// parent trace's `SampledFlag`, the `TraceIDRatioBased` sampler should be used
93// as a delegate of a `Parent` sampler.
94//
95//nolint:revive // revive complains about stutter of `trace.TraceIDRatioBased`
96func TraceIDRatioBased(fraction float64) Sampler {
97 if fraction >= 1 {
98 return AlwaysSample()
99 }
100
101 if fraction <= 0 {
102 fraction = 0
103 }
104
105 return &traceIDRatioSampler{
106 traceIDUpperBound: uint64(fraction * (1 << 63)),
107 description: fmt.Sprintf("TraceIDRatioBased{%g}", fraction),
108 }
109}
110
111type alwaysOnSampler struct{}
112
113func (as alwaysOnSampler) ShouldSample(p SamplingParameters) SamplingResult {
114 return SamplingResult{
115 Decision: RecordAndSample,
116 Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
117 }
118}
119
120func (as alwaysOnSampler) Description() string {
121 return "AlwaysOnSampler"
122}
123
124// AlwaysSample returns a Sampler that samples every trace.
125// Be careful about using this sampler in a production application with
126// significant traffic: a new trace will be started and exported for every
127// request.
128func AlwaysSample() Sampler {
129 return alwaysOnSampler{}
130}
131
132type alwaysOffSampler struct{}
133
134func (as alwaysOffSampler) ShouldSample(p SamplingParameters) SamplingResult {
135 return SamplingResult{
136 Decision: Drop,
137 Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
138 }
139}
140
141func (as alwaysOffSampler) Description() string {
142 return "AlwaysOffSampler"
143}
144
145// NeverSample returns a Sampler that samples no traces.
146func NeverSample() Sampler {
147 return alwaysOffSampler{}
148}
149
150// ParentBased returns a sampler decorator which behaves differently,
151// based on the parent of the span. If the span has no parent,
152// the decorated sampler is used to make sampling decision. If the span has
153// a parent, depending on whether the parent is remote and whether it
154// is sampled, one of the following samplers will apply:
155// - remoteParentSampled(Sampler) (default: AlwaysOn)
156// - remoteParentNotSampled(Sampler) (default: AlwaysOff)
157// - localParentSampled(Sampler) (default: AlwaysOn)
158// - localParentNotSampled(Sampler) (default: AlwaysOff)
159func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler {
160 return parentBased{
161 root: root,
162 config: configureSamplersForParentBased(samplers),
163 }
164}
165
166type parentBased struct {
167 root Sampler
168 config samplerConfig
169}
170
171func configureSamplersForParentBased(samplers []ParentBasedSamplerOption) samplerConfig {
172 c := samplerConfig{
173 remoteParentSampled: AlwaysSample(),
174 remoteParentNotSampled: NeverSample(),
175 localParentSampled: AlwaysSample(),
176 localParentNotSampled: NeverSample(),
177 }
178
179 for _, so := range samplers {
180 c = so.apply(c)
181 }
182
183 return c
184}
185
186// samplerConfig is a group of options for parentBased sampler.
187type samplerConfig struct {
188 remoteParentSampled, remoteParentNotSampled Sampler
189 localParentSampled, localParentNotSampled Sampler
190}
191
192// ParentBasedSamplerOption configures the sampler for a particular sampling case.
193type ParentBasedSamplerOption interface {
194 apply(samplerConfig) samplerConfig
195}
196
197// WithRemoteParentSampled sets the sampler for the case of sampled remote parent.
198func WithRemoteParentSampled(s Sampler) ParentBasedSamplerOption {
199 return remoteParentSampledOption{s}
200}
201
202type remoteParentSampledOption struct {
203 s Sampler
204}
205
206func (o remoteParentSampledOption) apply(config samplerConfig) samplerConfig {
207 config.remoteParentSampled = o.s
208 return config
209}
210
211// WithRemoteParentNotSampled sets the sampler for the case of remote parent
212// which is not sampled.
213func WithRemoteParentNotSampled(s Sampler) ParentBasedSamplerOption {
214 return remoteParentNotSampledOption{s}
215}
216
217type remoteParentNotSampledOption struct {
218 s Sampler
219}
220
221func (o remoteParentNotSampledOption) apply(config samplerConfig) samplerConfig {
222 config.remoteParentNotSampled = o.s
223 return config
224}
225
226// WithLocalParentSampled sets the sampler for the case of sampled local parent.
227func WithLocalParentSampled(s Sampler) ParentBasedSamplerOption {
228 return localParentSampledOption{s}
229}
230
231type localParentSampledOption struct {
232 s Sampler
233}
234
235func (o localParentSampledOption) apply(config samplerConfig) samplerConfig {
236 config.localParentSampled = o.s
237 return config
238}
239
240// WithLocalParentNotSampled sets the sampler for the case of local parent
241// which is not sampled.
242func WithLocalParentNotSampled(s Sampler) ParentBasedSamplerOption {
243 return localParentNotSampledOption{s}
244}
245
246type localParentNotSampledOption struct {
247 s Sampler
248}
249
250func (o localParentNotSampledOption) apply(config samplerConfig) samplerConfig {
251 config.localParentNotSampled = o.s
252 return config
253}
254
255func (pb parentBased) ShouldSample(p SamplingParameters) SamplingResult {
256 psc := trace.SpanContextFromContext(p.ParentContext)
257 if psc.IsValid() {
258 if psc.IsRemote() {
259 if psc.IsSampled() {
260 return pb.config.remoteParentSampled.ShouldSample(p)
261 }
262 return pb.config.remoteParentNotSampled.ShouldSample(p)
263 }
264
265 if psc.IsSampled() {
266 return pb.config.localParentSampled.ShouldSample(p)
267 }
268 return pb.config.localParentNotSampled.ShouldSample(p)
269 }
270 return pb.root.ShouldSample(p)
271}
272
273func (pb parentBased) Description() string {
274 return fmt.Sprintf("ParentBased{root:%s,remoteParentSampled:%s,"+
275 "remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}",
276 pb.root.Description(),
277 pb.config.remoteParentSampled.Description(),
278 pb.config.remoteParentNotSampled.Description(),
279 pb.config.localParentSampled.Description(),
280 pb.config.localParentNotSampled.Description(),
281 )
282}