Skip to content

Commit 65aab39

Browse files
pratik-mahallevitorvasc
authored andcommitted
Add guidance for custom samplers
Signed-off-by: Pratik Mahalle <mahallepratik683@gmail.com>
1 parent b06eab6 commit 65aab39

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

content/en/docs/languages/go/sampling.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,170 @@ By default, the tracer provider uses a `ParentBased` sampler with the
4242

4343
When in a production environment, consider using the `ParentBased` sampler with
4444
the `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

Comments
 (0)