Telemetry Export (OpenTelemetry / OTLP)
The Athena server can send its own metrics, traces and logs over
OTLP, the OpenTelemetry protocol, to an OpenTelemetry Collector or any other
backend that accepts OTLP. You set it up in Settings → Telemetry. It is an
extra output only: the Prometheus /metrics endpoint and the
System Health tab keep working as before. Changes apply when you save. No
restart is needed.
A new server has no OTLP exporter running. Athena only sends telemetry after you enable it
in the tab, in appsettings.json, or with an OTEL_* environment
variable. On an air-gapped network, point it at a collector
inside that network.
For the numbers the server shows in its own console and on /metrics, see
System Health & Prometheus Metrics. For the
server log files, see Server Logging.
Who can see and change it#
| To | You need | Built-in roles |
|---|---|---|
| See the Telemetry tab in Settings | settings.health.view, plus settings.manage to open the Settings page | Admin |
| Change, save or Test connection | settings.manage | Admin |
Read the settings and export status over REST (GET /api/settings/telemetry) | settings.health.view | Admin, Operator |
A custom role with settings.health.view but without settings.manage
sees the form as read-only, with the note "Read-only: changing these settings needs
settings.manage." See Permission Model & Role
Bundles.
Turning it on#
- Open Settings and click the Telemetry tab (right after
System Health), or go to
https://<server>:8443/settings/telemetry. - Tick Export telemetry over OTLP.
- Enter the collector's Endpoint and pick the Protocol.
- If your collector needs an API key or other header, enter it in Headers.
- Choose which Signals to send: Metrics, Traces and Logs. All three are on by default.
- Click Test connection. This sends one test span and one metric point using the values in the form, before they are saved.
- Click Save. The toast reads "Telemetry settings saved and applied." and the exporter starts at once.
Settings#
| Field | Default | What it does |
|---|---|---|
| Export telemetry over OTLP | Off | Turns the exporter on or off. |
| Endpoint | Blank. Then http://localhost:4317 for gRPC, http://localhost:4318 for HTTP | The collector's base URL. Must start with http:// or https:// and must not have a query string. For HTTP/protobuf, Athena adds /v1/traces, /v1/metrics and /v1/logs for you. For gRPC the URL is used as entered. |
| Protocol | gRPC | gRPC (usually port 4317) or HTTP/protobuf (usually port 4318). |
| Headers | None | key=value pairs separated by commas, for example api-key=… or Authorization=Basic …. See Headers are write-only. |
| Signals | Metrics, Traces and Logs all on | Which of the three to send. |
| Trace sampling ratio (0-1) | 1 | Parent-based. 1 keeps every trace, 0.1 keeps one in ten. |
| Log minimum level | Information | The lowest log level sent: Trace, Debug, Information, Warning, Error or Critical. This only affects the OTLP output, not the server's own log files. |
| Service name | athena-server | Sent as service.name. Use it to tell servers apart in your backend. |
| Deployment environment | None | Optional, for example production. Sent as deployment.environment. |
| Metric export interval (seconds) | 60 | How often metrics are pushed. 1 to 3600. |
Every export also carries service.version (the server version) and
service.instance.id (the server's machine name in lower case). Each export request
times out after 10 seconds.
If a value is not valid, Save shows one of these messages and nothing is changed:
- The endpoint must be an http:// or https:// URL.
- The endpoint must not have a query string.
- The trace sampling ratio must be between 0 and 1.
- The metric export interval must be 1 to 3600 seconds.
- Headers must be key=value pairs separated by commas.
Headers are write-only#
- Headers usually hold an API key, so they are stored encrypted and are never shown again. Once saved, the field is empty and its placeholder reads "Stored - leave blank to keep".
- Leave the field blank when you save to keep the stored headers. Type a new value to replace them.
- To delete them, tick Remove the stored headers and save.
- Over REST,
GETreturns********when headers are stored. Sending********or a blank value keeps them."clearHeaders": trueremoves them. - If the server can no longer decrypt the stored headers (for example after its key ring was replaced), it logs a warning and exports without them. Enter the headers again and save.
What is sent#
Only what the server is doing is sent, not the data it manages. Each signal is filtered before it leaves the server.
| Signal | Contents | Kept out |
|---|---|---|
| Metrics | The same allow-listed server metrics that /metrics publishes, with the same labels. | Any label that /metrics also drops. |
| Traces | One span per request the server handles (REST API, web console and agent gRPC calls) and per outgoing HTTP call. Every console page load is a ui.page span, and every Settings tab or agent-details section switch is a ui.subpage span, so the work a page does shows up in one trace. | Requests to /metrics and /health, static files, the console's live connection and other WebSocket traffic. On each span, only a fixed set of attributes is kept: method, status code, route template, scheme, protocol, port, error type and gRPC service/method/status, plus the page, trigger and tab of page spans. URL paths and query strings, client and server addresses, user agents, headers and exception messages are removed. |
| Logs | The server log events at or above Log minimum level. Each one carries the message template (for example Page {Route} loaded in {ElapsedMs} ms) and its values as separate attributes. Logs written inside a traced request carry its trace ID. | The fully rendered message. Values whose name suggests a secret (password, token, secret, cookie, API key, connection string, credential, authorization, header, query) are sent as redacted. URLs lose their query string. For an exception, only its type is sent. |
Export status#
The Status row on the Telemetry tab updates live after every export attempt. It shows the overall state, the endpoint, the time of the last successful export, and the failures since that success. Below it, one chip per enabled signal shows that signal's state and its failure count. While an export is failing, Last error shows the most recent error.
| State | Meaning |
|---|---|
| Off | Export is turned off. |
| Waiting | Export is on, but nothing has been sent yet. |
| Healthy | The last export of every enabled signal succeeded. |
| Failing | A signal has failed since its last success (or failed before its first success). |
| Down | A signal has been failing for more than 5 minutes, or every enabled signal is failing. |
- A toast appears once when export starts failing ("Telemetry export is failing: …") and once when it recovers ("Telemetry export recovered."). You do not get a toast for every retry.
- Saving the settings resets the counts, so an old endpoint's failures never show against a new one.
- The Telemetry export tile on Settings → System Health shows the same state. It reads "OTLP export is off" when the exporter is off.
- For alerting from Prometheus,
/metricspublishesathena_otlp_export_failures_total{signal}(a lifetime total, not reset on save) andathena_otlp_export_last_success_timestamp_seconds.
# Any OTLP export failures in the last 5 minutes
increase(athena_otlp_export_failures_total[5m])
Environment variables and appsettings.json#
The standard OpenTelemetry OTEL_* environment variables are honoured. For each field,
an environment variable wins over the Settings tab, and the tab wins over the
default. A field set by a variable is read-only in the tab and shows "Set by environment
variable …", so a container's configuration cannot be overridden by accident from the
console.
| Variable | Sets | Notes |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | Endpoint | Setting it also turns export on. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTEL_EXPORTER_OTLP_METRICS_ENDPOINTOTEL_EXPORTER_OTLP_LOGS_ENDPOINT | One signal's endpoint | Used exactly as given (nothing is appended). Setting any of them also turns export on. |
OTEL_EXPORTER_OTLP_PROTOCOL | Protocol | grpc or http/protobuf. |
OTEL_EXPORTER_OTLP_HEADERS | Headers | key=value,key2=value2. |
OTEL_METRICS_EXPORTEROTEL_TRACES_EXPORTEROTEL_LOGS_EXPORTER | Signals | none switches that signal off. Any other value leaves it on. |
OTEL_TRACES_SAMPLEROTEL_TRACES_SAMPLER_ARG | Trace sampling | always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off or parentbased_traceidratio. The ratio goes in _ARG. An unknown name is ignored. |
OTEL_SERVICE_NAME | Service name | |
OTEL_RESOURCE_ATTRIBUTES | Deployment environment and extra attributes | For example deployment.environment=production. |
OTEL_METRIC_EXPORT_INTERVAL | Metric export interval | In milliseconds, as in the OpenTelemetry specification (the tab uses seconds). |
OTEL_SDK_DISABLED=true | Everything | Forces export off, whatever else is set. |
Log minimum level has no environment variable. It always comes from the tab or from configuration.
You can also configure the exporter in the Telemetry:Otlp section of
appsettings.json, or with Telemetry__Otlp__* environment variables in a
container. The keys are Enabled, Endpoint, Protocol,
Headers, Metrics, Traces, Logs,
TraceSampleRatio, LogMinimumLevel, ServiceName,
DeploymentEnvironment and MetricExportIntervalSeconds. A plain-text
Headers value set this way works. The tab always stores headers encrypted.
{
"Telemetry": {
"Otlp": {
"Enabled": true,
"Endpoint": "http://otel-collector.corp.local:4317",
"Protocol": "grpc",
"ServiceName": "athena-server",
"DeploymentEnvironment": "production"
}
}
}
# docker-compose.yml: the same, with the standard OpenTelemetry variables
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
- OTEL_SERVICE_NAME=athena-server
- OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
- When you save in the tab, the settings are written to the server's
appsettings.local.jsonin its data directory. - Per server. Settings Sync does not copy telemetry settings, so each server keeps its own endpoint and its own API key.
- If the exporter cannot start at server start-up (for example a malformed endpoint), the
server logs the error and keeps running without the exporter. When it starts, the server log
shows
Telemetry: OTLP export ON to …with the endpoint, protocol and signals.
REST API#
| Endpoint | Permission | Does |
|---|---|---|
GET /api/settings/telemetry | settings.health.view | Returns the settings (headers masked), environmentOverrides (which fields an OTEL_* variable owns) and status (state, endpoint, last success, failures, last error, and the same per signal). |
PUT /api/settings/telemetry | settings.manage | Saves and applies the settings. Returns HTTP 400 with one of the messages above if a value is not valid. |
POST /api/settings/telemetry/test | settings.manage | Sends a test span and a metric point using the values in the body, without saving them. A failed test still returns HTTP 200, with success: false and the error in message. |
$headers = @{ Authorization = "Bearer $token" }
$t = (Invoke-RestMethod "https://athena.contoso.com:8443/api/settings/telemetry" `
-Headers $headers -SkipCertificateCheck).data
$t.status | Select-Object state, endpoint, lastSuccessAt, failures, lastError
$t.status.signals | Format-Table signal, state, failures, lastError
See API Reference for signing in and getting a token.
Audit trail#
Each save writes one SettingsChanged event per changed field, with the old and new
value and the user who saved. A change to the headers is recorded only as
[Hidden] → [Changed]. The header value itself is never recorded. See
Audit Event Types Reference.
Troubleshooting#
| Message or symptom | What to check |
|---|---|
Timed out waiting for the endpoint. with a localhost endpoint | localhost resolved to IPv6 (::1) and the collector listens on IPv4 only. Use 127.0.0.1 instead. |
| Unreachable: … | Nothing is listening at that address and port, or a firewall is in the way. Check the collector's host, port (4317 for gRPC, 4318 for HTTP) and the network path from the Athena server. |
| TLS handshake failed: … | The endpoint is https:// and the server does not trust the collector's certificate, or the collector does not speak TLS on that port. |
| HTTP 401 or gRPC status 16 (Unauthenticated) | The headers (API key) are missing or wrong. Enter them again and save. |
| HTTP 404 with HTTP/protobuf | Enter the collector's base URL only. Athena adds /v1/traces, /v1/metrics and /v1/logs itself. |
| Everything fails with the protocol set to gRPC on port 4318 (or HTTP on 4317) | The protocol and port don't match. Usually gRPC is 4317 and HTTP/protobuf is 4318. |
| A connection test is already running. | Only one test runs at a time. Wait for it to finish. |
| No answer from … within the timeout. | The collector accepted the connection but did not confirm both test items. Check the collector's own logs. |
| A field is greyed out with "Set by environment variable …" | That variable is set on the server or container and overrides the tab. Change or remove the variable and restart the server. |
| Traces arrive but some requests are missing | The Trace sampling ratio is below 1, or OTEL_TRACES_SAMPLER is set. Requests to /metrics, /health, static files and live-connection traffic are never traced. |
| Log records have no message text | By design, the message template is sent as the body and the values as attributes. Use the attributes in your backend. |
Related#
- System Health & Prometheus Metrics: the same counters in the console and on
/metrics. - Server Health & Monitoring: up/down health checks.
- Server Logging: the server's own log files and retention.
- Audit Forwarding to SIEM: send the audit trail to syslog.
- Data Directories & Backup: where saved settings live.