How to send OpenTelemetry traces to Elastic APM

Sending OpenTelemetry traces to Elastic APM connects vendor-neutral instrumentation to the Elastic service inventory and trace waterfall. The common cloud path is to point an OTLP-capable SDK or Collector at the Elastic Cloud Managed OTLP endpoint and authenticate it with an API key that can write APM events.

Elastic Cloud Managed OTLP accepts application telemetry from SDKs and Collectors without a separate APM Server. Self-managed, ECE, and ECK clusters need an exposed OTLP-compatible gateway such as the EDOT Collector, because the managed endpoint is not available for those deployment types.

The sample service name checkout-api and trace operation GET /checkout keep the smoke test easy to find in Kibana. Replace the endpoint and API key with values from the Elastic setup screen, keep credentials out of source control, and confirm the generated trace ID in ObservabilityApplications before reusing the same settings for a production service.

Steps to send OpenTelemetry traces to Elastic APM:

  1. Copy the Elastic Cloud Managed OTLP endpoint for the project or deployment.
    Endpoint: https://checkout-prod.ingest.us-east-1.aws.elastic.cloud

    Use the Ingest or Managed OTLP endpoint, not the Elasticsearch or Kibana endpoint. Elastic Cloud Hosted deployments need version 9.0 or later for the managed endpoint.

  2. Create an API key that can write APM events.
    {
      "otlp_writer": {
        "applications": [
          {
            "application": "apm",
            "resources": ["*"],
            "privileges": ["event:write"]
          }
        ]
      }
    }

    Kibana and the OpenTelemetry setup wizard return the encoded key value. Keep the space after ApiKey when building the OTLP authorization header.

  3. Export the OTLP settings in the shell that starts the trace sender.
    export OTEL_SERVICE_NAME="checkout-api"
    export OTEL_EXPORTER_OTLP_ENDPOINT="https://checkout-prod.ingest.us-east-1.aws.elastic.cloud"
    export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey <encoded-api-key>"
    export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
    export OTEL_TRACES_EXPORTER="otlp"

    For OTLP/HTTP, OpenTelemetry SDKs send trace payloads to /v1/traces under the base endpoint.

  4. Create a temporary Python environment for the smoke trace sender.
    $ python3 -m venv .otel-elastic-test
  5. Activate the temporary environment.
    $ . .otel-elastic-test/bin/activate
  6. Install the OpenTelemetry Python SDK and OTLP HTTP exporter.
    $ python -m pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
  7. Save a minimal trace sender as send-checkout-trace.py.
    send-checkout-trace.py
    import os
     
    from opentelemetry import trace
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
     
    service_name = os.getenv("OTEL_SERVICE_NAME", "checkout-api")
     
    resource = Resource.create(
        {
            "service.name": service_name,
            "service.version": "1.0.0",
            "deployment.environment": "production",
        }
    )
    provider = TracerProvider(resource=resource)
    provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
    trace.set_tracer_provider(provider)
     
    tracer = trace.get_tracer("checkout-smoke")
     
    with tracer.start_as_current_span("GET /checkout") as span:
        span.set_attribute("http.request.method", "GET")
        span.set_attribute("url.path", "/checkout")
        span.set_attribute("http.response.status_code", 200)
        context = span.get_span_context()
        trace_id = f"{context.trace_id:032x}"
        span_id = f"{context.span_id:016x}"
     
    provider.shutdown()
     
    print(f"sent trace_id={trace_id} span_id={span_id} service.name={service_name}")
  8. Run the trace sender.
    $ python send-checkout-trace.py
    sent trace_id=38857d40a5630e48dd3e4836ce5bfd31 span_id=ace2ff3f218a58eb service.name=checkout-api

    The command exits after the OTLP exporter flushes the span. Authentication failures usually mean the header is missing ApiKey, the key was copied incorrectly, or the key lacks apm event:write privilege.

  9. Open ObservabilityApplicationsService Inventory and filter for checkout-api.

    The service can take a minute to appear. Open the service and look for the printed trace ID or the GET /checkout operation in a trace sample.
    Related: How to check an APM service in Kibana

  10. Start the instrumented application with the same OTLP settings after the smoke trace appears.
    $ python -m checkout_service

    Use the command that normally starts the service for the chosen language or runtime. The service process must inherit the OTLP endpoint, authorization header, and service name.

  11. Remove the temporary smoke-test files.
    $ deactivate
    $ rm -rf .otel-elastic-test send-checkout-trace.py