Running a metrics export
Three keys, the egress admission the address does not grant you, what each exit status means, and the one limit to read before you schedule anything.
For the operator who runs the export and reads its result.
After reading it you can configure a collector, admit it, run one export cycle, and tell a partial success from a complete one.
Before you start: a collector that speaks OTLP/HTTP, and what the export gives you read once — it is the page that says which series exist.
saphan otel export runs one explicit export cycle. It reads the resolved workspace record,
sends the metrics to the configured collector, and prints both the row-source report and the
endpoint counters.
1 · Three keys
Set the destination before enabling export, so you never have an enabled exporter pointing nowhere.
| key | meaning |
|---|---|
otel.enabled | true or false. Absent means false — telemetry never starts because a key was left unset |
otel.endpoint_name | your label for the collector. It is the only endpoint identity that reaches the wire, and it rides the exporter's health points rather than the record metrics |
otel.endpoint_url | the collector's OTLP/HTTP metrics endpoint |
⛔ otel.endpoint_url accepts http or https only, and refuses a user:password@ prefix or a
query string. saphan config show prints this key verbatim, so a credential smuggled into the URL
would be published. Authentication is not configurable through this key — if your collector
needs credentials, put it behind something that supplies them.
2 · Setting the address grants no network access
⛔ The address is configuration. Reaching it is a separate, explicit permission. The collector must be admitted in the workspace's egress policy, by address and port.
⚠ This is the mistake operators actually make. When an export fails at the wire, check the egress admission before you suspect the collector — the exporter's own refusal reminds you of exactly this.
Validate the document the export command will actually load, rather than the one you think it will:
A collector on a private range is admissible and must be chosen deliberately — there is a configuration key that permits private ranges to be named. ⛔ Loopback and the cloud metadata endpoint stay refused regardless, and so does link-local space.
For a hostname, admission has two stages, and the distinction shows up in the refusal:
- the policy must admit the configured host and port;
- the exporter resolves the name once and applies the hard-deny and private-range rules to the address it got, then dials that exact approved address — it does not resolve the name again.
⇒ A refusal naming a denied address therefore means the hostname was admitted but its resolved address was not. Fix the collector's DNS, or admit the narrow private range deliberately.
⛔ Redirects are refused. Configure the final metrics URL, not something that forwards to it — the exporter does not follow a redirect even when the target would otherwise be admitted. That matters most for a collector behind a redirecting proxy.
See Egress control for the policy document itself.
3 · Run one cycle
It prints the row-source report — every metric it emitted, and every one it declined with the reason — followed by the per-endpoint counters:
| counter | meaning |
|---|---|
accepted | rows the exporter submitted |
exported | rows the collector answered successfully |
malformed | rows rejected before transmission, because the metric shape was invalid |
rejected | the collector received a valid request and rejected points |
failed, refused, buffered, dropped, depth | anything non-zero means the cycle did not fully land |
⇒ Anything non-zero beyond accepted and exported makes the command exit non-zero.
4 · What the exit status means
| exit | meaning | what to do |
|---|---|---|
| 0 | the cycle completed and no counter reports unaccepted or remaining rows | nothing — but see step 5 |
| 1 | configuration or the egress policy is missing or invalid | set the keys, create the policy, validate it, retry |
| 2 | the cycle ran, but the collector or the wire did not accept every row | check the collector is listening, the URL is right, and its host and port are admitted |
| 3 | otel.enabled is absent or false — no export was attempted | configure the endpoint, then enable it |
⚠ A disabled exporter refuses loudly and exits 3. It never exits 0 having done nothing, because a silent success is how an operator concludes telemetry is flowing when it is not.
5 · Confirm on the collector, not from the exit status
⛔ Exit 0 is the exporter's account of an HTTP response, not proof the collector kept anything. Confirm receipt independently — read the collector's own logs, or whatever sink you configured it to write.
What to read before you schedule anything
⛔ Every invocation exports the whole record. There is no cursor and no acknowledgement yet, so calling the command again replays every row, including delta sums. ⇒ Use it for an operator-requested reading. Do not schedule repeated calls until a cursor exists — a scheduled job against this build produces a dashboard that climbs because of the schedule rather than because of the fleet.
⚠ Operator-invoked only. There is no timer and no hook on the run path, and that is deliberate: an export must never slow a run down.
⚠ No authentication through the endpoint key, and ⚠ a successful export is not a delivery guarantee — both above.
Metrics export: what you get
Three metrics from the record to any OTLP collector you run, the rule that governs every label on them, and the metrics a monitoring team usually asks for that this deliberately refuses.
Notifications
One-way notices from your fleet to a chat destination: what they cost to turn on, and what they deliberately cannot do.