Observer
Observer Agent

Receive metrics over OpenTelemetry (OTLP)

Run the agent as an OTLP/HTTP receiver so any OpenTelemetry-compatible sender can push metrics into Observer.

The agent runs an HTTP server on port 4318 that accepts OpenTelemetry metric pushes. Any OTLP-compatible sender (OpenTelemetry SDK, OpenTelemetry Collector, vendor agent with an OTLP exporter) writes metrics to it; each metric you configure in Observer becomes a probe you can threshold on.

This is the only push-mode source. Every other source type (prometheus, http, tcp, etc.) pulls on a fixed interval. OTLP inverts the direction: the sender drives.

Listener configuration#

Set on the agent process:

# Bind address. Defaults to loopback so only same-host processes can
# push without an explicit token. Set to 0.0.0.0:4318 to accept pushes
# from other hosts; a bearer token becomes mandatory in that case.
OBSERVER_OTLP_LISTEN_ADDR=127.0.0.1:4318

# Required when binding a non-loopback address. The agent refuses to
# start otherwise.
OBSERVER_OTLP_BEARER_TOKEN=replace-with-a-32-byte-secret

# Max number of distinct streams (metric_name x attribute fingerprint)
# the receiver keeps in memory. Defaults to 1000.
OBSERVER_OTLP_MAX_BUFFER_POINTS=1000

# Skip the receiver entirely. Useful if you only run pull probes.
OBSERVER_OTLP_DISABLE=true

The receiver starts lazily the first time a metric definition with source_type: otlp is dispatched. If no such metric is configured, no port is opened.

When NOT to use this#

  • You already run Prometheus and the metric is exposed there. Use the prometheus source instead. It doesn't require wiring up a push pipeline and the agent doesn't need to bind a port.
  • You want server-side aggregation across multiple senders. The receiver records one data point per (metric_name, attribute fingerprint) stream. It does not merge samples across senders. Aggregate at your OpenTelemetry Collector with a groupbyattrs or transform processor before exporting.
  • You can't bound the cardinality of your attributes. The receiver caps streams at 1000 by default. A metric with unbounded user IDs or request IDs in its attributes will keep evicting itself. Strip high-cardinality attributes at the source.

Configure a metric in the Observer console#

Create a new metric, pick OpenTelemetry (OTLP) from the source picker, and fill in:

  • Metric name: the OTLP metric.name field your sender emits (http.server.duration, queue.depth, etc.). Exact match.
  • Attribute filters (optional): one key=value per line. Only data points carrying every key=value pair are read. Leave blank to read whichever matching data point is most recent.
  • Aggregation: how to collapse the data point into a single number. See Aggregation reference.
  • Staleness window: when the most recent matching sample is older than this, the probe surfaces no_data with reason otlp_stale. Default 120 seconds.

The threshold fields work the same as any other source: the aggregation produces a scalar; healthy and unhealthy bounds map that scalar to status.

Aggregation reference#

AggregationGauge / SumHistogramExponential histogram
latestthe data point valuemean (sum / count)mean
countthe data point valuethe bucket count totalthe count
sumthe data point valuethe sumthe sum
meanthe data point valuesum / countsum / count
p50 / p95 / p99the data point valuelinear interpolation across the bucket containing the target ranklinear interpolation across the positive / negative / zero buckets

For gauges and sums the aggregation field is effectively ignored because there is only one scalar to read. Pick latest to be explicit.

Sending from the OpenTelemetry Collector#

Add an OTLP/HTTP exporter to your collector config:

exporters:
  otlphttp/observer:
    endpoint: http://<agent-host>:4318
    encoding: json
    # Required only if the agent has OBSERVER_OTLP_BEARER_TOKEN set.
    headers:
      Authorization: "Bearer <your-token>"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/observer]

encoding: json is the v1 requirement. The default for otlphttp is protobuf; the agent rejects that with HTTP 415 so the collector logs make the cause obvious.

Sending from an application#

The OpenTelemetry SDKs ship OTLP exporters in every language binding. JavaScript example:


const exporter = new OTLPMetricExporter({
  url: "http://<agent-host>:4318/v1/metrics",
  headers: { Authorization: "Bearer <your-token>" },
});

Equivalent SDKs exist for Python, Go, Java, .NET, and Ruby. Each follows the same OTLP_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS environment variable contract.

Reason codes specific to OTLP#

The reason field on no_data results carries one of:

  • otlp_no_samples: nothing has arrived with this metric name and attribute filter since the agent started. Verify the sender is pointed at the right port and metric_name matches exactly.
  • otlp_stale: the most recent matching sample is older than the configured staleness window. The sender has stopped pushing, or the metric was renamed at the source.
  • otlp_aggregation_unavailable: the chosen aggregation is not extractable from the data point shape (e.g. a non-finite bucket base in a malformed exponential histogram). Switch to mean, count, or sum and verify the sender's bucket geometry.
  • otlp_receiver_disabled: the agent was started with OBSERVER_OTLP_DISABLE=true. Unset that variable and restart.
  • push_source_init_failed: the receiver could not start. Common cause: another process already bound the configured port. Free the port or change OBSERVER_OTLP_LISTEN_ADDR.

Security#

By default the receiver binds 127.0.0.1 only. Other processes on the same host can push metrics; the network cannot. The agent refuses to start with a non-loopback bind unless OBSERVER_OTLP_BEARER_TOKEN is set.

Authentication is a constant-time bearer-token compare against the Authorization: Bearer <token> header. Generate the token with 32 bytes of OS randomness (openssl rand -hex 32). Rotate by editing the env var and restarting the agent; the sender must update at the same time.

The receiver does not inspect TLS. If you need transport encryption across a network, terminate TLS at a sidecar reverse proxy and have the proxy forward to the loopback receiver. mTLS at the agent is a follow-up item.

Backpressure and limits#

The receiver buffers the most recent data point per stream (metric_name × attribute fingerprint). When the stream count exceeds OBSERVER_OTLP_MAX_BUFFER_POINTS, the oldest stream is dropped and a counter on the agent dashboard increments. There is no queue between the receiver and the buffer: each accepted request writes synchronously. Senders are never blocked.

For high-cardinality metric streams, prefer fewer distinct attribute combinations per metric or split into multiple metric names. The cap exists to bound memory growth on stuck-tag-explosion incidents.

Known limits#

These are intentional design decisions, surfaced here so you know which workloads to route elsewhere.

  • OTLP/gRPC on port 4317 is not implemented. The receiver returns HTTP 415 on application/x-protobuf. Configure your sender for OTLP/HTTP JSON.
  • OTLP traces and logs are not accepted. Observer's domain is status, and metrics drive status. Traces and logs go to a tracing / logging backend.
  • Exponential-histogram quantile estimation is geometry-based. The agent walks the positive, negative, and zero buckets in ascending numeric order using base = 2 ^ (2 ^ -scale) and interpolates within the target bucket. Accuracy depends on the sender's scale (higher scale = finer buckets = tighter estimate). Senders that emit only a few buckets per decade trade storage for precision; pick mean if the rough center is enough.

Troubleshooting#

Each entry leads with the symptom in bold and the action to take.

  • otlp_no_samples while the collector logs successful exports. Verify the metric name is byte-for-byte identical between your sender and the Observer metric definition. OTLP metric names are case-sensitive and . is significant. The collector's debug exporter (set verbosity: detailed) prints the names it received.
  • Sender gets 415 Unsupported Media Type. Your exporter is using protobuf. Set encoding: json on the otlphttp exporter (collector) or send the SDK to the JSON exporter package.
  • Sender gets Connection refused. The receiver only opens its port after the first OTLP metric definition is dispatched. In the console, create a metric with source type OpenTelemetry (OTLP) and save it, then retry the send within five seconds.
  • Receiver runs but no metrics show up in Observer. From the agent host: ss -tlnp | grep 4318 should list the agent process bound to the configured address. If it lists 127.0.0.1, a sender on another host can't reach it. Set OBSERVER_OTLP_LISTEN_ADDR=0.0.0.0:4318, set OBSERVER_OTLP_BEARER_TOKEN, restart the agent, and add Authorization: Bearer <token> to the sender.
  • otlp_aggregation_unavailable on a quantile (p50/p95/p99). Most often a malformed exponential histogram: the sender shipped a scale outside [-10, 20], or the bucket geometry produced non-finite bounds. Verify the sender's exporter configuration and the scale it picks; switch to mean, count, or sum if the geometry is fixed at the source.
Was this page helpful?