OTLP Configuration for OpenTelemetry C++

This document shows how to export telemetry to Uptrace using the OTLP exporter. For a quick introduction, see Getting started with OpenTelemetry C++.

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

Build requirements

Enable OTLP exporters when building OpenTelemetry C++ from source:

shell
cmake -DBUILD_TESTING=OFF \
      -DWITH_OTLP_HTTP=ON \
      -DWITH_OTLP_GRPC=ON \
      ..

Exporting Traces

OTLP/HTTP

cpp
#include <chrono>
#include <cstdlib>
#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h"
#include "opentelemetry/sdk/trace/batch_span_processor_factory.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"

namespace otlp = opentelemetry::exporter::otlp;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace trace_api = opentelemetry::trace;

void InitTracer() {
    otlp::OtlpHttpExporterOptions opts;
    opts.url = "https://api.uptrace.dev/v1/traces";

    const char* dsn = std::getenv("UPTRACE_DSN");
    opts.http_headers = {{"uptrace-dsn", dsn ? dsn : ""}};

    trace_sdk::BatchSpanProcessorOptions bsp_opts{};
    auto exporter = otlp::OtlpHttpExporterFactory::Create(opts);
    auto processor = trace_sdk::BatchSpanProcessorFactory::Create(
        std::move(exporter), bsp_opts);

    std::shared_ptr<trace_api::TracerProvider> provider =
        trace_sdk::TracerProviderFactory::Create(std::move(processor));
    trace_api::Provider::SetTracerProvider(provider);
}

OTLP/gRPC

cpp
#include <cstdlib>
#include "opentelemetry/exporters/otlp/otlp_grpc_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h"
#include "opentelemetry/sdk/trace/batch_span_processor_factory.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"

namespace otlp = opentelemetry::exporter::otlp;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace trace_api = opentelemetry::trace;

void InitTracer() {
    otlp::OtlpGrpcExporterOptions opts;
    opts.endpoint = "api.uptrace.dev:4317";

    const char* dsn = std::getenv("UPTRACE_DSN");
    opts.metadata = {{"uptrace-dsn", dsn ? dsn : ""}};
    opts.use_ssl_credentials = true;

    trace_sdk::BatchSpanProcessorOptions bsp_opts{};
    auto exporter = otlp::OtlpGrpcExporterFactory::Create(opts);
    auto processor = trace_sdk::BatchSpanProcessorFactory::Create(
        std::move(exporter), bsp_opts);

    std::shared_ptr<trace_api::TracerProvider> provider =
        trace_sdk::TracerProviderFactory::Create(std::move(processor));
    trace_api::Provider::SetTracerProvider(provider);
}

Exporting Metrics

cpp
#include <cstdlib>
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h"
#include "opentelemetry/sdk/metrics/meter_provider_factory.h"
#include "opentelemetry/sdk/metrics/meter_context_factory.h"
#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h"
#include "opentelemetry/metrics/provider.h"

namespace otlp = opentelemetry::exporter::otlp;
namespace metrics_sdk = opentelemetry::sdk::metrics;
namespace metrics_api = opentelemetry::metrics;

void InitMetrics() {
    otlp::OtlpHttpMetricExporterOptions opts;
    opts.url = "https://api.uptrace.dev/v1/metrics";

    const char* dsn = std::getenv("UPTRACE_DSN");
    opts.http_headers = {{"uptrace-dsn", dsn ? dsn : ""}};

    auto exporter = otlp::OtlpHttpMetricExporterFactory::Create(opts);

    metrics_sdk::PeriodicExportingMetricReaderOptions reader_opts;
    reader_opts.export_interval_millis = std::chrono::milliseconds(15000);
    reader_opts.export_timeout_millis = std::chrono::milliseconds(10000);

    auto reader = metrics_sdk::PeriodicExportingMetricReaderFactory::Create(
        std::move(exporter), reader_opts);

    auto context = metrics_sdk::MeterContextFactory::Create();
    context->AddMetricReader(std::move(reader));

    auto u_provider = metrics_sdk::MeterProviderFactory::Create(std::move(context));
    std::shared_ptr<metrics_api::MeterProvider> provider(std::move(u_provider));
    metrics_api::Provider::SetMeterProvider(provider);
}

Exporting Logs

cpp
#include <cstdlib>
#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h"
#include "opentelemetry/sdk/logs/logger_provider_factory.h"
#include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h"
#include "opentelemetry/logs/provider.h"

namespace otlp = opentelemetry::exporter::otlp;
namespace logs_sdk = opentelemetry::sdk::logs;
namespace logs_api = opentelemetry::logs;

void InitLogger() {
    otlp::OtlpHttpLogRecordExporterOptions opts;
    opts.url = "https://api.uptrace.dev/v1/logs";

    const char* dsn = std::getenv("UPTRACE_DSN");
    opts.http_headers = {{"uptrace-dsn", dsn ? dsn : ""}};

    auto exporter = otlp::OtlpHttpLogRecordExporterFactory::Create(opts);
    auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(
        std::move(exporter));

    std::shared_ptr<logs_api::LoggerProvider> provider =
        logs_sdk::LoggerProviderFactory::Create(std::move(processor));
    logs_api::Provider::SetLoggerProvider(provider);
}

Complete Example

cpp
#include "opentelemetry/trace/provider.h"
#include "opentelemetry/metrics/provider.h"
#include "opentelemetry/logs/provider.h"
#include "opentelemetry/sdk/trace/tracer_provider.h"
#include "opentelemetry/sdk/metrics/meter_provider.h"
#include "opentelemetry/sdk/logs/logger_provider.h"

namespace trace_api = opentelemetry::trace;
namespace metrics_api = opentelemetry::metrics;
namespace logs_api = opentelemetry::logs;

void Cleanup() {
    // Flush and shutdown TracerProvider
    auto trace_provider = trace_api::Provider::GetTracerProvider();
    if (trace_provider) {
        static_cast<opentelemetry::sdk::trace::TracerProvider*>(
            trace_provider.get())->ForceFlush();
    }

    // Flush and shutdown MeterProvider
    auto meter_provider = metrics_api::Provider::GetMeterProvider();
    if (meter_provider) {
        static_cast<opentelemetry::sdk::metrics::MeterProvider*>(
            meter_provider.get())->ForceFlush();
    }

    // Flush and shutdown LoggerProvider
    auto logger_provider = logs_api::Provider::GetLoggerProvider();
    if (logger_provider) {
        static_cast<opentelemetry::sdk::logs::LoggerProvider*>(
            logger_provider.get())->ForceFlush();
    }
}

int main() {
    InitTracer();
    InitMetrics();
    InitLogger();

    auto tracer = trace_api::Provider::GetTracerProvider()
        ->GetTracer("myservice", "1.0.0");

    auto span = tracer->StartSpan("main-operation");
    span->SetAttribute("key", "value");
    span->End();

    Cleanup();
    return 0;
}

What's next?