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
| Transport | Default port | Paths / services | Encoding |
|---|---|---|---|
| OTLP/HTTP | 4318 | POST /v1/logs, POST /v1/traces | application/x-protobuf, optionally Content-Encoding: gzip |
| OTLP/gRPC | 4317 | LogsService/Export, TraceService/Export | Protobuf |
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.
Collector configuration (OTLP/HTTP, recommended)
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 field | OBSESC field |
|---|---|
| Span name | body |
Resource service.name | service |
start_time_unix_nano | timestamp |
| end minus start | duration_ns and duration_ms attributes |
| Trace, span and parent span IDs | trace_id, span_id, parent_span_id attributes (lowercase hex) |
| Span kind | span.kind attribute, plus kind=span on every span |
Status ERROR | level=error, plus span.status_message when set |
| Span events and links | span.events and span.links attributes (compact JSON) |
| Span attributes | Attributes, 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 field | OBSESC field |
|---|---|
Resource attribute service.name | service |
body (string or bytes) | body |
time_unix_nano, else observed_time_unix_nano | timestamp. If both are zero, the node’s receive time is used |
| Log record attributes | Attributes. 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 record | trace_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
| Response | Meaning | What the Collector does |
|---|---|---|
200 | Batch durably stored | Moves on |
400 | Malformed protobuf, or the batch broke an ingest limit (for example an event over ingest.max_event_bytes) | Drops the batch. Check the node logs |
413 | Body over the size limit, or a gzip body that decompresses too far | Split into smaller batches |
415 | Not protobuf, or an unsupported Content-Encoding | Fix the exporter’s encoding |
503 + Retry-After / gRPC RESOURCE_EXHAUSTED | Node is applying backpressure or draining | Retries 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.