Docs: OpenTelemetry (OTLP)
Documentation / Ingestion

OpenTelemetry (OTLP)

Send logs and traces to OBSESC from the OpenTelemetry Collector or OTel SDKs over OTLP/HTTP or OTLP/gRPC.

OBSESC accepts OpenTelemetry logs and traces over both OTLP/HTTP and OTLP/gRPC. If you already run the OpenTelemetry Collector, add one exporter and put it in your logs and traces pipelines next to your current exporter.

Endpoints

TransportDefault portPaths / servicesEncoding
OTLP/HTTP4318POST /v1/logs, POST /v1/tracesapplication/x-protobuf, optionally Content-Encoding: gzip
OTLP/gRPC4317LogsService/Export, TraceService/ExportProtobuf

Metrics aren’t ingested, so don’t add OBSESC to a metrics pipeline.

OTLP/HTTP accepts protobuf only. A request with Content-Type: application/json is rejected with 415 Unsupported Media Type, so leave the exporter’s encoding at proto.

The otlphttp exporter appends /v1/logs and /v1/traces itself, so set the endpoint to the bare host and port:

receivers:
  otlp:
    protocols:
      grpc: {}
      http: {}

exporters:
  otlphttp/obsesc:
    endpoint: http://obsesc.your-domain.internal:4318
    encoding: proto
    # Only needed when the node enforces security.ingest_tokens:
    headers:
      authorization: "Bearer ${env:OBSESC_INGEST_TOKEN}"

  # Your existing exporter stays as it is, e.g.:
  # datadog:
  #   api:
  #     key: ${env:DD_API_KEY}

service:
  pipelines:
    logs:
      receivers: [otlp]
      exporters: [otlphttp/obsesc]          # e.g. [otlphttp/obsesc, datadog]
    traces:
      receivers: [otlp]
      exporters: [otlphttp/obsesc]

The HTTP listener decompresses gzip bodies, so you can leave the exporter’s default compression on.

Collector configuration (OTLP/gRPC)

exporters:
  otlp/obsesc:
    endpoint: obsesc.your-domain.internal:4317
    # The listener is plaintext unless TLS is configured on the node.
    # Remove this block once it does.
    tls:
      insecure: true
    # The gRPC listener doesn't accept compressed messages.
    compression: none
    # Only needed when the node enforces security.ingest_tokens:
    headers:
      authorization: "Bearer ${env:OBSESC_INGEST_TOKEN}"

Each gRPC message can be at most 4 MiB. If your Collector sends larger batches, use OTLP/HTTP (16 MiB per request) or lower the batch size.

Sending traces

OBSESC stores every span it receives as an ordinary event, and nothing is sampled. Send OBSESC the unsampled feed and keep any head or tail sampling on your existing exporter’s pipeline only. If you sample in a processor shared by both exporters, OBSESC only ever sees the sampled spans.

Each span becomes one event:

Span fieldOBSESC field
Span namebody
Resource service.nameservice
start_time_unix_nanotimestamp
end minus startduration_ns and duration_ms attributes
Trace, span and parent span IDstrace_id, span_id, parent_span_id attributes (lowercase hex)
Span kindspan.kind attribute, plus kind=span on every span
Status ERRORlevel=error, plus span.status_message when set
Span events and linksspan.events and span.links attributes (compact JSON)
Span attributesAttributes, copied as sent

If a span already has an attribute with the same name as one of the fields added above, the span’s own attribute is kept.

How log records are mapped

OTLP fieldOBSESC field
Resource attribute service.nameservice
body (string or bytes)body
time_unix_nano, else observed_time_unix_nanotimestamp. If both are zero, the node’s receive time is used
Log record attributesAttributes. Arrays and maps are stored as JSON strings
Resource attributes (host.name, deployment.environment, k8s.namespace.name and so on)Attributes. A record attribute with the same key wins
trace_id on the recordtrace_id attribute (32 lowercase hex)

Set service.name

An OTel SDK that never sets service.name reports unknown_service or unknown_service:<process>. OBSESC treats both as “no service” and places the events in ingest.default_service (default unknown). Set service.name on every resource, using the OTEL_SERVICE_NAME environment variable or a Collector resource processor.

If you’d rather derive the service from another attribute, set ingest.service_from on the node, for example k8s.namespace.name. It’s only used for events that arrive without a service.

Host, environment and namespace

The node reads the standard resource attributes into its four built-in dimensions: host, env, namespace and tenant. It checks host.name, deployment.environment, deployment.environment.name and k8s.namespace.name, among others. Your original attributes are kept as well. See Configuration examples to change which keys are used.

Responses and retries

ResponseMeaningWhat the Collector does
200Batch durably storedMoves on
400Malformed protobuf, or the batch broke an ingest limit (for example an event over ingest.max_event_bytes)Drops the batch. Check the node logs
413Body over the size limit, or a gzip body that decompresses too farSplit into smaller batches
415Not protobuf, or an unsupported Content-EncodingFix the exporter’s encoding
503 + Retry-After / gRPC RESOURCE_EXHAUSTEDNode is applying backpressure or drainingRetries with backoff

A retried request is only stored once, provided its log records carry their own timestamps (spans need valid trace and span IDs) and the retry arrives soon after the original. See Supported sources.

Verify

After a minute of traffic, open the console (the stack’s UiUrl output) and go to Data. The OTLP listeners should be listed under Data sources, and your service should appear on the Services page. For a guided first run, see Your first logs.