Direct OTLP Configuration for OpenTelemetry Go
This document shows how to export telemetry to Uptrace using the OTLP exporter directly. If you prefer the wrapper, see Getting started with OpenTelemetry Go.
Direct OTLP Configuration
If you prefer to use the OTLP exporter directly or are already using OpenTelemetry exporters, you can configure OpenTelemetry manually without the uptrace-go wrapper.
For more details on the OpenTelemetry APIs used below, see:
- OpenTelemetry Go Tracing API - creating spans, recording errors, adding attributes
- OpenTelemetry Go Metrics API - counters, histograms, and observers
- OpenTelemetry Go Resource detectors - service name, environment, and deployment metadata
Uptrace fully supports the OpenTelemetry Protocol (OTLP) over both gRPC and HTTP transports.
If you already have an OTLP exporter configured, you can continue using it with Uptrace by simply pointing it to the Uptrace OTLP endpoint.
Connecting to Uptrace
Choose an OTLP endpoint from the table below and pass your DSN via the uptrace-dsn header for authentication:
| Transport | Endpoint | Port |
|---|---|---|
| gRPC | https://api.uptrace.dev:4317 | 4317 |
| HTTP | https://api.uptrace.dev | 443 |
When using HTTP transport, you often need to specify the full URL for each signal type:
https://api.uptrace.dev/v1/traceshttps://api.uptrace.dev/v1/logshttps://api.uptrace.dev/v1/metrics
Note: Most OpenTelemetry SDKs support both transports. Use HTTP unless you're already familiar with gRPC.
Recommended Settings
For performance and reliability, we recommend:
- Use
BatchSpanProcessorandBatchLogProcessorfor batching spans and logs, reducing the number of export requests. - Enable
gzipcompression to reduce bandwidth usage. - Prefer
deltametrics temporality (Uptrace converts cumulative metrics automatically). - Use Protobuf encoding instead of JSON (Protobuf is more efficient and widely supported).
- Use HTTP transport for simplicity and fewer configuration issues (unless you're already familiar with gRPC).
- Optionally, use the AWS X-Ray ID generator to produce trace IDs compatible with AWS X-Ray.
Common Environment Variables
You can use environment variables to configure resource attributes and propagators::
| Variable | Description |
|---|---|
OTEL_RESOURCE_ATTRIBUTES | Comma-separated resource attributes, e.g., service.name=myservice,service.version=1.0.0. |
OTEL_SERVICE_NAME=myservice | Sets the service.name attribute (overrides OTEL_RESOURCE_ATTRIBUTES). |
OTEL_PROPAGATORS | Comma-separated list of context propagators (default: tracecontext,baggage). |
Most language SDKs allow configuring the OTLP exporter entirely via environment variables:
# Endpoint (choose HTTP or gRPC)
export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev" # HTTP
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev:4317" # gRPC
# Pass DSN for authentication
export OTEL_EXPORTER_OTLP_HEADERS="uptrace-dsn=<FIXME>"
# Performance optimizations
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=BASE2_EXPONENTIAL_BUCKET_HISTOGRAM
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA
Configure BatchSpanProcessor to balance throughput and payload size:
export OTEL_BSP_EXPORT_TIMEOUT=10000 # Max export timeout (ms)
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=10000 # Avoid >32MB payloads
export OTEL_BSP_MAX_QUEUE_SIZE=30000 # Adjust for available memory
export OTEL_BSP_MAX_CONCURRENT_EXPORTS=2 # Parallel exports
Exporting Traces
This example shows how to configure the OTLP trace exporter to send distributed traces to Uptrace. For information on creating spans and using the tracing API, see OpenTelemetry Go Tracing.
Here is the complete example:
package main
import (
"context"
"fmt"
"os"
"go.opentelemetry.io/contrib/propagators/aws/xray"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func main() {
ctx := context.Background()
dsn := os.Getenv("UPTRACE_DSN")
if dsn == "" {
panic("UPTRACE_DSN environment variable is required")
}
fmt.Println("using DSN:", dsn)
exporter, err := otlptracehttp.New(
ctx,
otlptracehttp.WithEndpoint("https://api.uptrace.dev"),
otlptracehttp.WithHeaders(map[string]string{
// Set the Uptrace DSN here or use UPTRACE_DSN env var.
"uptrace-dsn": dsn,
}),
otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
)
if err != nil {
panic(err)
}
bsp := sdktrace.NewBatchSpanProcessor(exporter,
sdktrace.WithMaxQueueSize(10_000),
sdktrace.WithMaxExportBatchSize(10_000))
// Call shutdown to flush the buffers when program exits.
defer bsp.Shutdown(ctx)
resource, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
attribute.String("service.name", "myservice"),
attribute.String("service.version", "1.0.0"),
))
if err != nil {
panic(err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(resource),
sdktrace.WithIDGenerator(xray.NewIDGenerator()),
)
tracerProvider.RegisterSpanProcessor(bsp)
// Install our tracer provider and we are done.
otel.SetTracerProvider(tracerProvider)
tracer := otel.Tracer("app_or_package_name")
ctx, span := tracer.Start(ctx, "main")
defer span.End()
fmt.Printf("trace: https://app.uptrace.dev/traces/%s\n", span.SpanContext().TraceID())
}
Exporting Metrics
This example shows how to configure the OTLP metrics exporter to send OpenTelemetry metrics to Uptrace. For information on creating counters, histograms, and observers, see OpenTelemetry Go Metrics.
Here is the complete example:
package main
import (
"context"
"fmt"
"os"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/metric"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
"google.golang.org/grpc/encoding/gzip"
)
func main() {
ctx := context.Background()
dsn := os.Getenv("UPTRACE_DSN")
if dsn == "" {
panic("UPTRACE_DSN environment variable is required")
}
fmt.Println("using DSN:", dsn)
exporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint("https://api.uptrace.dev:4317"),
otlpmetricgrpc.WithHeaders(map[string]string{
// Set the Uptrace DSN here or use UPTRACE_DSN env var.
"uptrace-dsn": dsn,
}),
otlpmetricgrpc.WithCompressor(gzip.Name),
otlpmetricgrpc.WithTemporalitySelector(preferDeltaTemporalitySelector),
)
if err != nil {
panic(err)
}
reader := sdkmetric.NewPeriodicReader(
exporter,
sdkmetric.WithInterval(15*time.Second),
)
resource, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
attribute.String("service.name", "myservice"),
attribute.String("service.version", "1.0.0"),
))
if err != nil {
panic(err)
}
provider := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(reader),
sdkmetric.WithResource(resource),
)
otel.SetMeterProvider(provider)
meter := provider.Meter("app_or_package_name")
counter, _ := meter.Int64Counter(
"uptrace.demo.counter_name",
metric.WithUnit("1"),
metric.WithDescription("counter description"),
)
fmt.Println("exporting data to Uptrace...")
for {
counter.Add(ctx, 1)
time.Sleep(time.Millisecond)
}
}
func preferDeltaTemporalitySelector(kind sdkmetric.InstrumentKind) metricdata.Temporality {
switch kind {
case sdkmetric.InstrumentKindCounter,
sdkmetric.InstrumentKindObservableCounter,
sdkmetric.InstrumentKindHistogram:
return metricdata.DeltaTemporality
default:
return metricdata.CumulativeTemporality
}
}
Exporting Logs
This example shows how to configure the OTLP logs exporter to send logs to Uptrace. The logs are correlated with traces using the active span context. For alternative logging approaches, see OpenTelemetry Zap and OpenTelemetry Logrus.
Here is the complete example:
package main
import (
"context"
"fmt"
"log/slog"
"os"
"time"
"go.opentelemetry.io/contrib/bridges/otelslog"
"go.opentelemetry.io/contrib/propagators/aws/xray"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/log/global"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
func main() {
ctx := context.Background()
dsn := os.Getenv("UPTRACE_DSN")
if dsn == "" {
panic("UPTRACE_DSN environment variable is required")
}
fmt.Println("using DSN:", dsn)
resource, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
attribute.String("service.name", "myservice"),
attribute.String("service.version", "1.0.0"),
))
if err != nil {
panic(err)
}
shutdownTracing := configureTracing(ctx, dsn, resource)
defer shutdownTracing()
shutdownLogging := configureLogging(ctx, dsn, resource)
defer shutdownLogging()
tracer := otel.Tracer("app_or_package_name")
logger := otelslog.NewLogger("app_or_package_name")
ctx, main := tracer.Start(ctx, "main-operation", trace.WithSpanKind(trace.SpanKindServer))
defer main.End()
logger.ErrorContext(ctx, "hello world", slog.String("error", "error message"))
fmt.Printf("trace: https://app.uptrace.dev/traces/%s\n", main.SpanContext().TraceID())
}
func configureLogging(ctx context.Context, dsn string, resource *resource.Resource) func() {
exp, err := otlploghttp.New(ctx,
otlploghttp.WithEndpoint("https://api.uptrace.dev"),
otlploghttp.WithHeaders(map[string]string{
"uptrace-dsn": dsn,
}),
otlploghttp.WithCompression(otlploghttp.GzipCompression),
)
if err != nil {
panic(err)
}
bsp := sdklog.NewBatchProcessor(exp,
sdklog.WithMaxQueueSize(10_000),
sdklog.WithExportMaxBatchSize(10_000),
sdklog.WithExportInterval(10*time.Second),
sdklog.WithExportTimeout(10*time.Second),
)
provider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(bsp),
sdklog.WithResource(resource),
)
global.SetLoggerProvider(provider)
return func() {
provider.Shutdown(ctx)
}
}
func configureTracing(ctx context.Context, dsn string, resource *resource.Resource) func() {
exporter, err := otlptracehttp.New(
ctx,
otlptracehttp.WithEndpoint("https://api.uptrace.dev"),
otlptracehttp.WithHeaders(map[string]string{
"uptrace-dsn": dsn,
}),
otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
)
if err != nil {
panic(err)
}
bsp := sdktrace.NewBatchSpanProcessor(exporter,
sdktrace.WithMaxQueueSize(10_000),
sdktrace.WithMaxExportBatchSize(10_000),
)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(resource),
sdktrace.WithIDGenerator(xray.NewIDGenerator()),
)
tracerProvider.RegisterSpanProcessor(bsp)
otel.SetTracerProvider(tracerProvider)
return func() {
tracerProvider.Shutdown(ctx)
}
}