Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add the `WithGinMetricAttributes` option to allow setting dynamic, per-request metric attributes based on `*gin.Context` in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6932)
- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095)
- Added test for Fields in `go.opentelemetry.io/contrib/propagators/jaeger`. (#7119)
- Allow configuring samplers in `go.opentelemetry.io/contrib/otelconf`. (#7148)

### Changed

Expand Down
8 changes: 7 additions & 1 deletion otelconf/testdata/v0.3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -290,29 +290,35 @@ tracer_provider:
event_attribute_count_limit: 128
# Configure max attributes per span link.
link_attribute_count_limit: 128
# Configure the sampler.
# If omitted, parent based sampler with a root of always_on is used.
sampler:
# Configure sampler to be parent_based.
parent_based:
# Configure root sampler.
# If omitted or null, always_on is used.
root:
# Configure sampler to be trace_id_ratio_based.
trace_id_ratio_based:
# Configure trace_id_ratio.
# If omitted or null, 1.0 is used.
ratio: 0.0001
# Configure remote_parent_sampled sampler.
# If omitted or null, always_on is used.
remote_parent_sampled:
# Configure sampler to be always_on.
always_on: {}
# Configure remote_parent_not_sampled sampler.
# If omitted or null, always_off is used.
remote_parent_not_sampled:
# Configure sampler to be always_off.
always_off: {}
# Configure local_parent_sampled sampler.
# If omitted or null, always_on is used.
local_parent_sampled:
# Configure sampler to be always_on.
always_on: {}
# Configure local_parent_not_sampled sampler.
# If omitted or null, always_off is used.
local_parent_not_sampled:
# Configure sampler to be always_off.
always_off: {}
Expand Down
82 changes: 82 additions & 0 deletions otelconf/v0.3.0/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"go.opentelemetry.io/otel/trace/noop"
)

var errInvalidSamplerConfiguration = errors.New("invalid sampler configuration")

func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) {
if cfg.opentelemetryConfig.TracerProvider == nil {
return noop.NewTracerProvider(), noopShutdown, nil
Expand All @@ -37,13 +39,93 @@ func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProv
errs = append(errs, err)
}
}
if s, err := sampler(cfg.opentelemetryConfig.TracerProvider.Sampler); err == nil {
opts = append(opts, sdktrace.WithSampler(s))
} else {
errs = append(errs, err)
}
if len(errs) > 0 {
return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...)
}
tp := sdktrace.NewTracerProvider(opts...)
return tp, tp.Shutdown, nil
}

func parentBasedSampler(s *SamplerParentBased) (sdktrace.Sampler, error) {
var rootSampler sdktrace.Sampler
var opts []sdktrace.ParentBasedSamplerOption
var errs []error
var err error

if s.Root == nil {
rootSampler = sdktrace.AlwaysSample()
} else {
rootSampler, err = sampler(s.Root)
if err != nil {
errs = append(errs, err)
}
}
if s.RemoteParentSampled != nil {
remoteParentSampler, err := sampler(s.RemoteParentSampled)
if err != nil {
errs = append(errs, err)
} else {
opts = append(opts, sdktrace.WithRemoteParentSampled(remoteParentSampler))
}
}
if s.RemoteParentNotSampled != nil {
remoteParentNotSampler, err := sampler(s.RemoteParentNotSampled)
if err != nil {
errs = append(errs, err)
} else {
opts = append(opts, sdktrace.WithRemoteParentNotSampled(remoteParentNotSampler))
}
}
if s.LocalParentSampled != nil {
localParentSampler, err := sampler(s.LocalParentSampled)
if err != nil {
errs = append(errs, err)
} else {
opts = append(opts, sdktrace.WithLocalParentSampled(localParentSampler))
}
}
if s.LocalParentNotSampled != nil {
localParentNotSampler, err := sampler(s.LocalParentNotSampled)
if err != nil {
errs = append(errs, err)
} else {
opts = append(opts, sdktrace.WithLocalParentNotSampled(localParentNotSampler))
}
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
return sdktrace.ParentBased(rootSampler, opts...), nil
}

func sampler(s *Sampler) (sdktrace.Sampler, error) {
if s == nil {
// If omitted, parent based sampler with a root of always_on is used.
return sdktrace.ParentBased(sdktrace.AlwaysSample()), nil
}
if s.ParentBased != nil {
return parentBasedSampler(s.ParentBased)
}
if s.AlwaysOff != nil {
return sdktrace.NeverSample(), nil
}
if s.AlwaysOn != nil {
return sdktrace.AlwaysSample(), nil
}
if s.TraceIDRatioBased != nil {
if s.TraceIDRatioBased.Ratio == nil {
return sdktrace.TraceIDRatioBased(1), nil
}
return sdktrace.TraceIDRatioBased(*s.TraceIDRatioBased.Ratio), nil
}
return nil, errInvalidSamplerConfiguration
}

func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) {
if exporter.Console != nil && exporter.OTLP != nil {
return nil, errors.New("must not specify multiple exporters")
Expand Down
143 changes: 143 additions & 0 deletions otelconf/v0.3.0/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ func TestTracerPovider(t *testing.T) {
wantProvider: noop.NewTracerProvider(),
wantErr: errors.Join(errors.New("must not specify multiple span processor type"), errors.New("must not specify multiple exporters")),
},
{
name: "invalid-sampler-config",
cfg: configOptions{
opentelemetryConfig: OpenTelemetryConfiguration{
TracerProvider: &TracerProvider{
Processors: []SpanProcessor{
{
Simple: &SimpleSpanProcessor{
Exporter: SpanExporter{
Console: Console{},
},
},
},
},
Sampler: &Sampler{},
},
},
},
wantProvider: noop.NewTracerProvider(),
wantErr: errors.Join(errInvalidSamplerConfiguration),
},
}
for _, tt := range tests {
tp, shutdown, err := tracerProvider(tt.cfg, resource.Default())
Expand Down Expand Up @@ -699,3 +720,125 @@ func TestSpanProcessor(t *testing.T) {
})
}
}

func TestSampler(t *testing.T) {
for _, tt := range []struct {
name string
sampler *Sampler
wantSampler sdktrace.Sampler
wantError error
}{
{
name: "no sampler configuration, return default",
sampler: nil,
wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()),
},
{
name: "invalid sampler configuration, return error",
sampler: &Sampler{},
wantSampler: nil,
wantError: errInvalidSamplerConfiguration,
},
{
name: "sampler configuration always on",
sampler: &Sampler{
AlwaysOn: SamplerAlwaysOn{},
},
wantSampler: sdktrace.AlwaysSample(),
},
{
name: "sampler configuration always off",
sampler: &Sampler{
AlwaysOff: SamplerAlwaysOff{},
},
wantSampler: sdktrace.NeverSample(),
},
{
name: "sampler configuration trace ID ratio",
sampler: &Sampler{
TraceIDRatioBased: &SamplerTraceIDRatioBased{
Ratio: ptr(0.54),
},
},
wantSampler: sdktrace.TraceIDRatioBased(0.54),
},
{
name: "sampler configuration trace ID ratio no ratio",
sampler: &Sampler{
TraceIDRatioBased: &SamplerTraceIDRatioBased{},
},
wantSampler: sdktrace.TraceIDRatioBased(1),
},
{
name: "sampler configuration parent based no options",
sampler: &Sampler{
ParentBased: &SamplerParentBased{},
},
wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()),
},
{
name: "sampler configuration parent based many options",
sampler: &Sampler{
ParentBased: &SamplerParentBased{
Root: &Sampler{
AlwaysOff: SamplerAlwaysOff{},
},
RemoteParentNotSampled: &Sampler{
AlwaysOn: SamplerAlwaysOn{},
},
RemoteParentSampled: &Sampler{
TraceIDRatioBased: &SamplerTraceIDRatioBased{
Ratio: ptr(0.009),
},
},
LocalParentNotSampled: &Sampler{
AlwaysOff: SamplerAlwaysOff{},
},
LocalParentSampled: &Sampler{
TraceIDRatioBased: &SamplerTraceIDRatioBased{
Ratio: ptr(0.05),
},
},
},
},
wantSampler: sdktrace.ParentBased(
sdktrace.NeverSample(),
sdktrace.WithLocalParentNotSampled(sdktrace.NeverSample()),
sdktrace.WithLocalParentSampled(sdktrace.TraceIDRatioBased(0.05)),
sdktrace.WithRemoteParentNotSampled(sdktrace.AlwaysSample()),
sdktrace.WithRemoteParentSampled(sdktrace.TraceIDRatioBased(0.009)),
),
},
{
name: "sampler configuration with many errors",
sampler: &Sampler{
ParentBased: &SamplerParentBased{
Root: &Sampler{},
RemoteParentNotSampled: &Sampler{},
RemoteParentSampled: &Sampler{},
LocalParentNotSampled: &Sampler{},
LocalParentSampled: &Sampler{},
},
},
wantError: errors.Join(
errInvalidSamplerConfiguration,
errInvalidSamplerConfiguration,
errInvalidSamplerConfiguration,
errInvalidSamplerConfiguration,
errInvalidSamplerConfiguration,
),
},
} {
t.Run(tt.name, func(t *testing.T) {
got, err := sampler(tt.sampler)
if tt.wantError != nil {
require.Error(t, err)
require.EqualError(t, err, tt.wantError.Error())
} else {
require.NoError(t, err)
}

require.Equal(t, tt.wantSampler, got)
})
}
}