The most surprising thing about sending OpenTelemetry data to Elastic APM is that you don’t actually send it directly to Elastic APM; you send it to the OpenTelemetry Collector, which then transforms and forwards it.

Let’s see this in action. Imagine you have a simple Node.js application instrumented with OpenTelemetry. It’s generating traces and metrics. By default, this data might be going nowhere or to a local file. We want it in Elastic APM for analysis.

First, your application needs to know where to send its OTLP (OpenTelemetry Protocol) data. This is usually configured via environment variables.

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http"

Here, localhost:4318 is where our OpenTelemetry Collector is listening for incoming OTLP data over HTTP.

Next, the OpenTelemetry Collector itself needs to be configured. This is done via a YAML file, typically named otel-collector-config.yaml. This configuration tells the Collector what to do with the data it receives.

receivers:
  otlp:
    protocols:
      http:
      grpc:

processors:
  batch:
    timeout: 1s
    send_batch_size: 1000

exporters:
  logging: # For debugging, sends data to collector's stdout
    loglevel: debug
  elasticapm:
    service_name: "my-node-app"
    server_url: "http://localhost:8200" # Elastic APM Server URL

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, elasticapm]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, elasticapm]

In this configuration:

  • Receivers: We’re enabling the otlp receiver to listen for both HTTP and gRPC.
  • Processors: The batch processor is crucial. It groups telemetry data into batches before sending it to an exporter. This significantly improves efficiency by reducing the number of network requests. timeout: 1s means it will send data at least every second, and send_batch_size: 1000 means it will send data if 1000 items are collected.
  • Exporters:
    • logging: This is a handy exporter for development. It prints the data the collector receives and processes to its standard output, allowing you to verify that data is arriving and in the expected format.
    • elasticapm: This is the core exporter for our goal. It takes the processed OpenTelemetry data and translates it into a format that the Elastic APM Server understands. service_name is how your application will appear in Elastic APM, and server_url points to your APM Server.

To run the collector, you’d typically use its binary or a Docker image:

# Assuming you have the collector binary in your PATH
otelcol --config otel-collector-config.yaml

Or with Docker:

docker run -p 4318:4318 -v $(pwd)/otel-collector-config.yaml:/etc/otel-collector-config.yaml otel/opentelemetry-collector:latest --config /etc/otel-collector-config.yaml

Once running, your instrumented application, configured with OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318", will send its OTLP data to the collector. The collector, using the elasticapm exporter, will then forward this data to your Elastic APM Server at http://localhost:8200. You’ll then see your application’s traces and metrics appear in the Elastic APM UI.

The "bridge" aspect is the OpenTelemetry Collector itself. It’s not just a dumb pipe; it’s a sophisticated agent that can receive data in one format (OTLP), process it (batching, filtering, adding attributes), and export it in another format (Elastic APM’s proprietary format, or OTLP to an APM Server that supports it). The elasticapm exporter specifically handles the translation of OpenTelemetry semantic conventions into Elastic APM’s domain model.

What most people don’t realize is that the elasticapm exporter in the OpenTelemetry Collector doesn’t just forward OTLP data directly. It actively transforms OpenTelemetry span attributes, event names, and other data points into their Elastic APM equivalents. For example, an http.method attribute in OpenTelemetry might be mapped to an http.verb field in Elastic APM, and error.type might become error.class. This mapping ensures that the rich context captured by OpenTelemetry is correctly represented and searchable within Elastic APM’s specialized data model.

The next logical step after successfully sending data is to explore how to filter and sample data within the collector to manage ingestion volume and cost.

Want structured learning?

Take the full Elastic-apm course →