How to Configure Trace Sampling Strategies in Fabrica-Kit: A Complete Guide
Fabrica-Kit uses a default OpenTelemetry ParentBased sampler with 100% trace retention, but you can customize the sampling strategy by modifying the Init function in trace/trace.go or by creating a custom tracer provider with your preferred sampler.
Fabrica-Kit provides built-in OpenTelemetry tracing utilities for Go microservices in the go-pantheon/fabrica-kit repository. While the default configuration captures every trace for maximum observability, production environments often require trace sampling strategies to manage costs and performance overhead.
Understanding the Default Trace Sampling Behavior
Fabrica-Kit initializes its tracer provider in trace/trace.go with a hard-coded sampling configuration that captures all traces.
The Default Sampler Configuration
According to the source code at [line 27 of trace/trace.go](https://github.com/go-pantheon/fabrica-kit/blob/main/trace/trace.go#L27), the default implementation uses:
tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0)))
This ParentBased sampler wraps a TraceIDRatioBased sampler set to 1.0, meaning every trace is recorded regardless of load. The Init function defined at line 17 applies this configuration automatically when you initialize the tracer.
Method 1: Modifying the Built-in Init Function
The most direct approach to configure trace sampling is refactoring the Init function to accept a sampler parameter instead of using the hard-coded value.
Code Implementation
Modify trace/trace.go to accept a tracesdk.Sampler argument:
package trace
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
// Init now accepts a sampler argument.
func Init(url, name, profile, color string, sampler tracesdk.Sampler) error {
exporter, err := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint(url),
otlptracehttp.WithInsecure(),
)
if err != nil {
return err
}
// Use the caller-provided sampler instead of the hard-coded one.
tp := tracesdk.NewTracerProvider(
tracesdk.WithSampler(sampler), // ← custom sampler
tracesdk.WithBatcher(exporter),
tracesdk.WithResource(resource.NewSchemaless(
semconv.ServiceNameKey.String(name),
attribute.String("profile", profile),
attribute.String("color", color),
)),
)
otel.SetTracerProvider(tp)
return nil
}
Usage Example
Pass your desired sampler when initializing the tracer:
import (
"go.opentelemetry.io/otel/sdk/trace"
"github.com/go-pantheon/fabrica-kit/trace"
)
func main() {
// Sample 50% of traces, but keep the parent-based logic.
sampler := trace.ParentBased(trace.TraceIDRatioBased(0.5))
if err := trace.Init(
"localhost:4318", "my-service", "prod", "blue", sampler,
); err != nil {
panic(err)
}
}
Method 2: Creating a Custom Tracer Provider
If you prefer not to modify the library's source code, create a standalone tracer provider that overrides the global configuration while preserving Fabrica-Kit's instrumentation helpers.
Standalone Provider Setup
Create your own provider with custom sampling logic:
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
func main() {
exp, _ := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure(),
)
// Example: always sample (useful in development)
sampler := tracesdk.AlwaysSample()
tp := tracesdk.NewTracerProvider(
tracesdk.WithSampler(sampler),
tracesdk.WithBatcher(exp),
tracesdk.WithResource(resource.NewSchemaless(
semconv.ServiceNameKey.String("my-service"),
attribute.String("profile", "dev"),
attribute.String("color", "green"),
)),
)
otel.SetTracerProvider(tp)
// Existing Fabrica-Kit helpers now pick up this provider automatically.
// e.g. traceredis.StartConnectionSpan(ctx, "GET", "redis:6379", 0)
}
This approach ensures that utilities like traceredis.StartConnectionSpan from trace/traceredis/redis.go and PostgreSQL instrumentation from trace/tracepg/postgresql.go automatically respect your custom sampling configuration.
Available Sampling Strategies
OpenTelemetry provides several built-in samplers you can use with Fabrica-Kit:
AlwaysSample()– Records every span. Best for local development or debugging sessions where complete visibility is required.AlwaysOff()– Drops all spans. Use this in production environments when tracing overhead must be eliminated entirely.TraceIDRatioBased(p)– Records a random fraction of traces based on the probabilityp(e.g.,0.1for 10%). Ideal for cost-controlled production tracing at scale.ParentBased(inner)– Inherits the sampling decision from the parent span, falling back to theinnersampler for root spans. Ensures distributed traces remain complete when any part is sampled.- Custom composite samplers – Combine multiple strategies, such as always sampling error traces while applying ratio-based sampling to successful requests.
How Sampling Affects Fabrica-Kit Components
When you configure trace sampling strategies in fabrica-kit, the change propagates to all instrumentation that relies on the global tracer provider:
trace/traceredis/redis.go– Redis client operations will follow your sampling rules automaticallytrace/tracepg/postgresql.go– PostgreSQL connection pooling and query spans respect the configured sampler
Because these components use the global provider set via otel.SetTracerProvider, no additional configuration is required after you initialize the tracer with your chosen sampler.
Summary
- Fabrica-Kit defaults to 100% trace sampling via
ParentBased(TraceIDRatioBased(1.0))intrace/trace.go - Modify the
Initfunction to accept sampler parameters for a permanent solution within the library - Create a custom provider to override sampling without modifying source files
- Available strategies include probability-based, parent-based, always-on, and always-off samplers
- All Fabrica-Kit instrumentation utilities automatically respect the global tracer provider configuration
Frequently Asked Questions
What is the default trace sampling rate in fabrica-kit?
Fabrica-Kit captures all traces by default. The Init function in trace/trace.go configures a ParentBased sampler wrapping a TraceIDRatioBased(1.0) sampler, resulting in 100% retention regardless of traffic volume.
How do I reduce trace volume in production?
Replace the default sampler with TraceIDRatioBased(0.1) to sample approximately 10% of traces, or use ParentBased(TraceIDRatioBased(0.1)) to maintain parent-child trace consistency. Wrap this in the Init function or set it in a custom provider before calling otel.SetTracerProvider.
Can I use different sampling strategies for different services?
Yes. Since the sampler is configured per-process during tracer initialization, each service can pass a different tracesdk.Sampler to the Init function or create its own provider. For example, critical services might use AlwaysSample() while high-traffic services use TraceIDRatioBased(0.05).
Does changing the sampler affect existing instrumentation?
No code changes are required in existing instrumentation. Components like traceredis and tracepg retrieve the tracer from the global provider via otel.GetTracerProvider(). When you update the sampling strategy during initialization, all existing spans automatically follow the new rules.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →