Provider options and defaults¶
Each signal package exposes one constructor with the same shape. Options are functional and optional; every default below applies when you pass none.
tracing.NewProvider¶
func NewProvider(
ctx context.Context,
res *resource.Resource,
s otelcore.Settings,
opts ...Option,
) (*sdktrace.TracerProvider, error)
Returns a *sdktrace.TracerProvider with a batch span processor over the OTLP/HTTP
trace exporter, the supplied resource, and a parent-based ratio sampler.
| Option | Default | Range | Effect |
|---|---|---|---|
tracing.WithSampling(ratio float64) |
tracing.DefaultSampling = 0.1 |
clamped, see below | Head sampling ratio, wrapped in ParentBased |
What WithSampling does at the edges¶
The ratio is passed to sdktrace.TraceIDRatioBased, which does not reject an
out-of-range value. It clamps:
| Value | Behaviour |
|---|---|
>= 1.0 |
every root trace is recorded and sampled |
<= 0.0 (including negative) |
every root trace is dropped |
| between | that fraction of trace IDs is sampled |
No error is returned for a nonsense ratio, so a typo like WithSampling(10) silently
means "sample everything" and WithSampling(-1) silently means "sample nothing".
Why the sampler is parent-based¶
The ratio sampler is wrapped in sdktrace.ParentBased, so the decision is only made for
a root span. A span that arrives with a sampled parent is always recorded, and one
with an unsampled parent is always dropped. A trace therefore stays whole across service
boundaries instead of being decided again at each hop. That is what makes 10% mean
"10% of complete traces" rather than "10% of every span independently".
One consequence catches people out: for a service that only ever receives requests
carrying trace context, its own WithSampling ratio changes nothing. The decision was
made upstream.
OTEL_TRACES_SAMPLER has no effect¶
NewProvider always passes sdktrace.WithSampler(...), and that option overrides
OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG. Setting them changes nothing; use
WithSampling instead. See
Environment variables.
metrics.NewProvider¶
func NewProvider(
ctx context.Context,
res *resource.Resource,
s otelcore.Settings,
opts ...Option,
) (*sdkmetric.MeterProvider, error)
Returns a *sdkmetric.MeterProvider with a PeriodicReader pushing to the OTLP/HTTP
metric exporter.
| Option | Default | Effect |
|---|---|---|
metrics.WithInterval(d time.Duration) |
metrics.DefaultInterval = 60s |
Interval between periodic exports |
A duration of zero or less is ignored by the SDK's WithInterval, which then leaves the
interval at whatever it already held. So WithInterval(0) is a no-op, not an error and
not "export continuously".
There is no option for the per-export timeout. It stays at the SDK's default of 30
seconds, and unlike the interval it can be changed with
OTEL_METRIC_EXPORT_TIMEOUT.
OTEL_METRIC_EXPORT_INTERVAL has no effect¶
NewProvider always passes sdkmetric.WithInterval(...), with DefaultInterval when
you supply no option, and that overrides OTEL_METRIC_EXPORT_INTERVAL. An operator
setting the variable to one second still gets exports every sixty.
logs.NewProvider and logs.Handler¶
func NewProvider(
ctx context.Context,
res *resource.Resource,
s otelcore.Settings,
) (*sdklog.LoggerProvider, error)
func Handler(lp *sdklog.LoggerProvider, name string) slog.Handler
NewProvider takes no options. There is no variadic parameter to add one to, so
gaining one later is a signature change. It returns a provider with a batch processor
over the OTLP/HTTP log exporter.
Handler wraps the provider in otelslog.NewHandler(name, ...), where name becomes
the instrumentation scope on every record. Three things about it are worth knowing:
- It replaces your output, it does not add to it.
slog.New(logs.Handler(lp, …))produces a logger that writes only to OTLP. Nothing reaches stderr. To keep local output you need your own fan-out handler; this module does not ship one. - It applies no severity floor.
slog's usualLevelInfodefault belongs to the text and JSON handlers, not to this bridge, so alogger.Debug(...)call is exported. Filter withslog.HandlerOptionson a handler you control, or by not calling. - It takes the concrete
*sdklog.LoggerProvider, not thelog.LoggerProviderinterface, so a no-op provider cannot be substituted in tests.
Trace correlation depends on how you call the logger, not on how you build it. See Correlate logs with traces.
Defaults that come from the OpenTelemetry SDK¶
These are not this module's values; they are what the SDK uses because this module
passes no option for them. They move when the pinned OTel version moves. All were
re-checked on 2026-10-12 against otel/sdk, sdk/log and the trace and metric OTLP/HTTP
exporters at v1.47.0, and the log exporter at v0.23.0.
Span batching¶
| Setting | Default | Tunable with |
|---|---|---|
| Max queue size | 2048 spans | OTEL_BSP_MAX_QUEUE_SIZE |
| Schedule delay | 5s | OTEL_BSP_SCHEDULE_DELAY |
| Export timeout | 30s | OTEL_BSP_EXPORT_TIMEOUT |
| Max export batch size | 512 spans | OTEL_BSP_MAX_EXPORT_BATCH_SIZE |
Log-record batching¶
| Setting | Default | Tunable with |
|---|---|---|
| Max queue size | 2048 records | OTEL_BLRP_MAX_QUEUE_SIZE |
| Export interval | 1s | OTEL_BLRP_SCHEDULE_DELAY |
| Export timeout | 30s | OTEL_BLRP_EXPORT_TIMEOUT |
| Max export batch size | 512 records | OTEL_BLRP_MAX_EXPORT_BATCH_SIZE |
OTLP/HTTP transport, all three signals¶
| Setting | Default | Tunable with |
|---|---|---|
| Collector host and port when nothing is configured | localhost:4318 |
OTEL_EXPORTER_OTLP_ENDPOINT |
| Request timeout | 10s | OTEL_EXPORTER_OTLP_TIMEOUT |
| Compression | none | OTEL_EXPORTER_OTLP_COMPRESSION |
| HTTP proxy | http.ProxyFromEnvironment |
HTTPS_PROXY, HTTP_PROXY, NO_PROXY |
| Max serialised request size before compression | 64 MiB | nothing; the SDK's WithMaxRequestSize is not exposed here |
| Retry | enabled; 5s initial, 30s max interval, 60s max elapsed | nothing; the SDK's WithRetry is not exposed here |
The retry budget is worth reading twice: a batch that cannot be delivered within 60 seconds of retrying is discarded, silently as far as your service is concerned.
Who owns Shutdown¶
Every factory returns a provider whose Shutdown the caller owns. None of them
registers a shutdown hook, a signal handler or a finaliser.
Skipping it loses whatever is still buffered: up to five seconds of spans, one second of
log records, and up to a full interval of metrics. Call it with a timeout, because
Shutdown will sit through the retry budget above if the collector is unreachable:
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = tp.Shutdown(shutdownCtx)
_ = mp.Shutdown(shutdownCtx)
_ = lp.Shutdown(shutdownCtx)