Skip to content

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 usual LevelInfo default belongs to the text and JSON handlers, not to this bridge, so a logger.Debug(...) call is exported. Filter with slog.HandlerOptions on a handler you control, or by not calling.
  • It takes the concrete *sdklog.LoggerProvider, not the log.LoggerProvider interface, 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)