OTLP Configuration for OpenTelemetry Swift

This document shows how to configure the OTLP exporter in OpenTelemetry Swift to send telemetry data to Uptrace. For a quick setup, see Getting started with OpenTelemetry Swift.

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:

TransportEndpointPort
gRPChttps://api.uptrace.dev:43174317
HTTPhttps://api.uptrace.dev443

When using HTTP transport, you often need to specify the full URL for each signal type:

  • https://api.uptrace.dev/v1/traces
  • https://api.uptrace.dev/v1/logs
  • https://api.uptrace.dev/v1/metrics

Note: Most OpenTelemetry SDKs support both transports. Use HTTP unless you're already familiar with gRPC.

For performance and reliability, we recommend:

  • Use BatchSpanProcessor and BatchLogProcessor for batching spans and logs, reducing the number of export requests.
  • Enable gzip compression to reduce bandwidth usage.
  • Prefer delta metrics 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::

VariableDescription
OTEL_RESOURCE_ATTRIBUTESComma-separated resource attributes, e.g., service.name=myservice,service.version=1.0.0.
OTEL_SERVICE_NAME=myserviceSets the service.name attribute (overrides OTEL_RESOURCE_ATTRIBUTES).
OTEL_PROPAGATORSComma-separated list of context propagators (default: tracecontext,baggage).

Most language SDKs allow configuring the OTLP exporter entirely via environment variables:

shell
# 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:

shell
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

OTLP over gRPC

The gRPC exporter is the recommended approach for server-side Swift applications.

Dependencies

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.0.0"),
    .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0"),
],
targets: [
    .executableTarget(
        name: "MyApp",
        dependencies: [
            .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
            .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
            .product(name: "OpenTelemetryProtocolExporter", package: "opentelemetry-swift"),
            .product(name: "ResourceExtension", package: "opentelemetry-swift"),
            .product(name: "GRPC", package: "grpc-swift"),
        ]
    )
]

Exporting Traces

swift
import Foundation
import GRPC
import NIO
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporter
import ResourceExtension

func configureTracing(dsn: String) {
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

    // Connect to Uptrace gRPC endpoint
    let channel = ClientConnection
        .usingPlatformAppropriateTLS(for: group)
        .connect(host: "api.uptrace.dev", port: 4317)

    // Configure with Uptrace DSN header
    let otlpConfig = OtlpConfiguration(
        timeout: OtlpConfiguration.DefaultTimeoutInterval,
        headers: [("uptrace-dsn", dsn)]
    )

    let traceExporter = OtlpTraceExporter(
        channel: channel,
        config: otlpConfig
    )

    // Use BatchSpanProcessor for production
    let spanProcessor = BatchSpanProcessor(spanExporter: traceExporter)

    // Configure resource
    let resource = DefaultResources().get().merging(other: Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string("myservice"),
        ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("1.0.0"),
    ]))

    OpenTelemetry.registerTracerProvider(tracerProvider:
        TracerProviderBuilder()
            .add(spanProcessor: spanProcessor)
            .with(resource: resource)
            .build()
    )
}

// Usage
func main() {
    let dsn = ProcessInfo.processInfo.environment["UPTRACE_DSN"] ?? ""
    configureTracing(dsn: dsn)

    let tracer = OpenTelemetry.instance.tracerProvider.get(
        instrumentationName: "MyApp",
        instrumentationVersion: "1.0.0"
    )

    let span = tracer.spanBuilder(spanName: "main").startSpan()
    defer { span.end() }

    print("trace_id: \(span.context.traceId.hexString)")
}

Exporting Metrics

swift
import Foundation
import GRPC
import NIO
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporter

func configureMetrics(dsn: String) {
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

    let channel = ClientConnection
        .usingPlatformAppropriateTLS(for: group)
        .connect(host: "api.uptrace.dev", port: 4317)

    let otlpConfig = OtlpConfiguration(
        timeout: OtlpConfiguration.DefaultTimeoutInterval,
        headers: [("uptrace-dsn", dsn)]
    )

    let metricExporter = OtlpMetricExporter(
        channel: channel,
        config: otlpConfig
    )

    // Configure resource
    let resource = DefaultResources().get().merging(other: Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string("myservice"),
        ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("1.0.0"),
    ]))

    OpenTelemetry.registerMeterProvider(meterProvider:
        MeterProviderBuilder()
            .with(processor: MetricProcessorSdk())
            .with(exporter: metricExporter)
            .with(resource: resource)
            .build()
    )
}

// Usage
func recordMetrics() {
    let meter = OpenTelemetry.instance.meterProvider.get(
        instrumentationName: "MyApp",
        instrumentationVersion: "1.0.0"
    )

    let counter = meter.createIntCounter(name: "requests_total")
    counter.add(value: 1, labels: ["method": "GET", "status": "200"])
}

OTLP over HTTP

HTTP transport is simpler to configure and works well for iOS/mobile applications where gRPC dependencies may be problematic.

Dependencies

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.0.0"),
],
targets: [
    .executableTarget(
        name: "MyApp",
        dependencies: [
            .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
            .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
            .product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"),
            .product(name: "ResourceExtension", package: "opentelemetry-swift"),
        ]
    )
]

Exporting Traces via HTTP

swift
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporterHTTP
import ResourceExtension

func configureTracingHTTP(dsn: String) {
    // Configure HTTP exporter
    let traceExporter = OtlpHttpTraceExporter(
        endpoint: URL(string: "https://api.uptrace.dev/v1/traces")!,
        config: OtlpConfiguration(
            timeout: 10.0,
            headers: [("uptrace-dsn", dsn)]
        )
    )

    let spanProcessor = BatchSpanProcessor(spanExporter: traceExporter)

    let resource = DefaultResources().get().merging(other: Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string("myservice"),
        ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("1.0.0"),
    ]))

    OpenTelemetry.registerTracerProvider(tracerProvider:
        TracerProviderBuilder()
            .add(spanProcessor: spanProcessor)
            .with(resource: resource)
            .build()
    )
}

Span Processors

OpenTelemetry Swift provides two span processors:

Batches spans before export, reducing network overhead:

swift
let spanProcessor = BatchSpanProcessor(
    spanExporter: traceExporter,
    scheduleDelay: 5.0,           // Export interval in seconds
    maxQueueSize: 2048,           // Maximum queue size
    maxExportBatchSize: 512       // Maximum batch size per export
)

SimpleSpanProcessor

Exports spans immediately. Use only for debugging:

swift
let spanProcessor = SimpleSpanProcessor(spanExporter: traceExporter)

Multiple Processors

You can use multiple processors simultaneously:

swift
let tracerProvider = TracerProviderBuilder()
    .add(spanProcessor: BatchSpanProcessor(spanExporter: otlpExporter))
    .add(spanProcessor: SimpleSpanProcessor(spanExporter: StdoutExporter()))
    .with(resource: resource)
    .build()

Exporters

OpenTelemetry Swift provides several exporters:

ExporterDescription
OtlpTraceExporterOTLP/gRPC trace exporter
OtlpMetricExporterOTLP/gRPC metrics exporter
OtlpHttpTraceExporterOTLP/HTTP trace exporter
StdoutExporterExports to console (debugging)
InMemoryExporterKeeps data in memory (testing)
JaegerExporterExports to Jaeger
ZipkinTraceExporterExports to Zipkin

iOS/Mobile Considerations

For iOS and mobile applications:

  1. Use HTTP transport - Simpler dependencies, no gRPC overhead
  2. Configure background export - Handle app lifecycle events
  3. Respect data usage - Batch aggressively on cellular connections
  4. Handle offline scenarios - Use persistence exporter for reliability

Persistence Exporter

For mobile apps, consider using the persistence exporter to handle network interruptions:

swift
// Wrap your exporter with persistence
let persistentExporter = PersistenceExporter(
    exporter: traceExporter,
    storageURL: FileManager.default.temporaryDirectory
)

What's next?