Send usage, errors and billing corrections to your OpenTelemetry collector

Administrator·5 minutes to read

What this does for you

The activity export sends your organisation's model usage to the OpenTelemetry collector you already run, so your own dashboards can show who used which model, how many tokens it took, what it is estimated to cost, which runs failed and which charges were corrected. Only an owner or an administrator can see or change it. On the hosted product the export is sent by Botseon's worker; on the home edition it leaves from the owner's own machine.

No message text is ever part of these records: not the prompt, not the reply, not a provider's error message. Conversation content has its own two switches on the same screen and is off unless both are on.

Before you start

  • A collector that takes OTLP over HTTP with JSON bodies, at a public https address. Logs are posted to /v1/logs and metrics to /v1/metrics under the address you enter.
  • If the collector sits behind a firewall, read the activity export's addresses first, and add both of its addresses (IPv4 and IPv6) to the collector's allowlist. The Outbound addresses part of the Activity export screen shows the same pair. A home installation sends from its own machine instead.

Steps

  1. Open Activity export (/telemetry), or Organisation → Audit and scroll to it.
  2. Enter the Collector endpoint and any Headers your collector needs (for example x-api-key). Headers are stored encrypted and never shown again.
  3. Press Save and turn on, then Send a test event to check the collector accepts it.
  4. Under What is sent, turn off any kind of event you do not want. Everything is on until you turn it off, and a kind added in a later release is sent from the first minute after it ships.

The export runs every 30 seconds and reads a call once it is five seconds old, so new activity reaches the collector within a minute and a call still being written is never sent half done.

What each switch sends

SwitchSignalWhat arrives
Model callslog, event.name = model.callone record per metered model call: run, bot, member (enduser.id), model, route, credential kind, tokens by type, latency, finish reason, and botseon.cost.estimate_eur
Run errorslog, event.name = run.errorone record per run that failed: its failure kind only (a fixed word, or unknown), never the message
Billing correctionslog, event.name = billing.correctionone record per credit-ledger row that corrects a charge after the fact (refund, adjustment, overshoot_credit, overshoot_recovered): its ledger id, kind, source, signed botseon.billing.amount_eur, reason word and the usage it corrects
Token usage by typemetric botseon.usage.tokens ({token})a delta sum per model, credential kind, bot, member and botseon.token.type (input, cached_input, cache_write, output, reasoning)
Cost estimatemetric botseon.usage.cost_estimate (EUR)a delta sum per model, credential kind, bot and member

Every metric point carries the organisation as the resource attribute botseon.org.id.

How the two metrics add up

Both metrics are delta sums (aggregationTemporality = 1, monotonic): each post carries only what happened in its own window, from startTimeUnixNano to timeUnixNano, and the next window starts exactly where the last one ended. Add the points up over any period to get the total for that period; do not take the latest value as a running total.

  • Each call is counted once. A window with no new calls sends nothing rather than a zero.
  • A retry is the same post. If the collector is down or answers 429 or 5xx, the window is kept and posted again, unchanged, on the next pass, before any newer window. Your collector may see the same window twice only if it stored it and still answered with an error; drop a repeat by its start and end time.
  • A refused window is not resent. A 4xx other than 429 means the collector will never take it, so it is counted under dropped on the screen and the export moves on.
  • Windows waiting for the collector are held by the sending worker, up to 32 of them. If the collector stays down past that, the oldest waiting window is dropped and counted under dropped; a restart of the worker while a window waits also loses that window. The model call log records and the billing ledger stay the record to reconcile against.

The cost estimate is not an invoice

botseon.usage.cost_estimate and botseon.cost.estimate_eur are the cost of each call at the price pinned when it ran, before credits, allowances and corrections. Your invoice is what the billing pages say. A correction made later arrives as a billing correction record; it does not change a cost estimate already sent.

If something goes wrong

  • Nothing arrives. Press Send a test event; the screen says whether the address was refused, the collector answered with an error, or it could not be reached.
  • The dropped count grows. The collector is refusing or cannot keep up. Fix it, and new activity flows again on the next pass; what was dropped is not sent later.
  • A kind is missing. Check its switch under What is sent. Turning a switch back on sends activity from that moment; what happened while it was off is not sent afterwards.

Last verified against build a60904f.