@@ -42,3 +42,170 @@ By default, the tracer provider uses a `ParentBased` sampler with the
4242
4343When in a production environment, consider using the ` ParentBased ` sampler with
4444the ` TraceIDRatioBased ` sampler.
45+
46+ ## Custom samplers
47+
48+ If the built-in samplers don't meet your needs, you can create a custom sampler
49+ by implementing the
50+ [ ` Sampler ` ] ( https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#Sampler )
51+ interface. A custom sampler must implement two methods:
52+
53+ - ` ShouldSample(parameters SamplingParameters) SamplingResult ` : Makes the
54+ sampling decision based on the provided parameters.
55+ - ` Description() string ` : Returns a description of the sampler.
56+
57+ ### Implementing the Sampler interface
58+
59+ Here's the structure you need to implement:
60+
61+ ``` go
62+ type Sampler interface {
63+ ShouldSample (parameters SamplingParameters ) SamplingResult
64+ Description () string
65+ }
66+ ```
67+
68+ The
69+ [ ` SamplingParameters ` ] ( https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#SamplingParameters )
70+ struct provides context about the span being created:
71+
72+ ``` go
73+ type SamplingParameters struct {
74+ ParentContext context.Context
75+ TraceID trace.TraceID
76+ Name string
77+ Kind trace.SpanKind
78+ Attributes []attribute.KeyValue
79+ Links []trace.Link
80+ }
81+ ```
82+
83+ ### Handling SamplingResult correctly
84+
85+ The
86+ [ ` SamplingResult ` ] ( https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#SamplingResult )
87+ you return from ` ShouldSample ` has three fields:
88+
89+ ``` go
90+ type SamplingResult struct {
91+ Decision SamplingDecision
92+ Attributes []attribute.KeyValue
93+ Tracestate trace.TraceState
94+ }
95+ ```
96+
97+ - ** Decision** : One of ` Drop ` , ` RecordOnly ` , or ` RecordAndSample ` .
98+ - ** Attributes** : Additional attributes to add to the span.
99+ - ** Tracestate** : The tracestate to use for the span.
100+
101+ {{% alert title="Critical: Preserve tracestate" color="warning" %}}
102+
103+ When implementing a custom sampler, you ** must** preserve the parent's
104+ tracestate in your ` SamplingResult ` . Failing to do so breaks context propagation
105+ in distributed systems that rely on tracestate for passing vendor-specific or
106+ application-specific trace data.
107+
108+ Always extract the tracestate from the parent span context:
109+
110+ ``` go
111+ psc := trace.SpanContextFromContext (parameters.ParentContext )
112+ // Use psc.TraceState() in your SamplingResult
113+ ```
114+
115+ {{% /alert %}}
116+
117+ ### Example
118+
119+ The following example demonstrates a custom sampler that samples spans based on
120+ an attribute value while correctly preserving tracestate:
121+
122+ ``` go
123+ package main
124+
125+ import (
126+ " context"
127+
128+ " go.opentelemetry.io/otel/attribute"
129+ sdktrace " go.opentelemetry.io/otel/sdk/trace"
130+ " go.opentelemetry.io/otel/trace"
131+ )
132+
133+ // AttributeBasedSampler samples spans based on an attribute value.
134+ // It always samples spans with the "high.priority" attribute set to true.
135+ type AttributeBasedSampler struct {
136+ fallback sdktrace.Sampler
137+ }
138+
139+ // NewAttributeBasedSampler creates a new AttributeBasedSampler.
140+ func NewAttributeBasedSampler (fallback sdktrace .Sampler ) *AttributeBasedSampler {
141+ return &AttributeBasedSampler{fallback: fallback}
142+ }
143+
144+ func (s *AttributeBasedSampler ) ShouldSample (p sdktrace .SamplingParameters ) sdktrace .SamplingResult {
145+ // Always extract the parent span context to get the tracestate.
146+ psc := trace.SpanContextFromContext (p.ParentContext )
147+
148+ // Check if any attribute indicates high priority.
149+ for _ , attr := range p.Attributes {
150+ if attr.Key == " high.priority" && attr.Value .AsBool () {
151+ return sdktrace.SamplingResult {
152+ Decision: sdktrace.RecordAndSample ,
153+ Attributes: p.Attributes ,
154+ // Critical: preserve the parent's tracestate
155+ Tracestate: psc.TraceState (),
156+ }
157+ }
158+ }
159+
160+ // Fall back to the default sampler for other spans.
161+ result := s.fallback .ShouldSample (p)
162+
163+ // Ensure tracestate is preserved even when using fallback.
164+ // Built-in samplers already handle this, but it's good practice to verify.
165+ return sdktrace.SamplingResult {
166+ Decision: result.Decision ,
167+ Attributes: result.Attributes ,
168+ Tracestate: psc.TraceState (),
169+ }
170+ }
171+
172+ func (s *AttributeBasedSampler ) Description () string {
173+ return " AttributeBasedSampler"
174+ }
175+ ```
176+
177+ ### Using your custom sampler
178+
179+ You can use your custom sampler with the tracer provider:
180+
181+ ``` go
182+ sampler := NewAttributeBasedSampler (sdktrace.TraceIDRatioBased (0.1 ))
183+
184+ provider := sdktrace.NewTracerProvider (
185+ sdktrace.WithSampler (sampler),
186+ )
187+ ```
188+
189+ You can also compose it with the ` ParentBased ` sampler:
190+
191+ ``` go
192+ provider := sdktrace.NewTracerProvider (
193+ sdktrace.WithSampler (
194+ sdktrace.ParentBased (
195+ NewAttributeBasedSampler (sdktrace.TraceIDRatioBased (0.1 )),
196+ ),
197+ ),
198+ )
199+ ```
200+
201+ ### Additional considerations
202+
203+ When implementing custom samplers, keep these points in mind:
204+
205+ 1 . ** Ignoring the parent sampling decision** : If you want to respect parent
206+ sampling decisions, wrap your sampler with ` ParentBased ` or check
207+ ` psc.IsSampled() ` manually.
208+
209+ 2 . ** Heavy computations in ShouldSample** : The ` ShouldSample ` function is called
210+ for every span creation. Avoid expensive operations like network calls or
211+ complex computations that could impact performance.
0 commit comments