# Emitted telemetry

> Every span, metric, log record, and resource attribute Elide emits on its own: names, instrumentation scopes, units, attributes, and when each is recorded.

What Elide records without code changes, once a destination is configured. Telemetry your code creates keeps the
names and scopes you give it.

## Resource

Every span, metric, log record, and profile from one process carries the same resource.

| Attribute | Value |
| --- | --- |
| `service.name` | `OTEL_SERVICE_NAME`, else `service.name` in `elide.pkl`, else `unknown_service:elide` |
| `service.version` | `service.version` |
| `service.namespace` | `service.namespace` |
| `deployment.environment.name` | `service.environment` |
| `telemetry.sdk.name` | `elide` |
| `telemetry.sdk.version` | The Elide version |
| any | `resource` in `elide.pkl` and `OTEL_RESOURCE_ATTRIBUTES` |

When the same attribute is set in more than one place, flags win over variables, and variables over `elide.pkl`.

## Instrumentation scopes

| Scope | Signals |
| --- | --- |
| `dev.elide.runtime` | HTTP server and client spans, `elide build`, `elide test`, and `elide install` spans, runtime metrics, error records, Elide's own log records |
| `dev.elide.runtime.profiler` | Guest CPU profiles |
| `console` | Captured `console.*` output (JavaScript) |
| logger name, e.g. `root`, `app.db` | Captured Python `logging` records |

## Spans

### HTTP server spans

One `SERVER` span per request served by `elide serve`, a `fetch` handler, or a `node:http` server, when
`runtime.traces` is on.

| Field | Value |
| --- | --- |
| Name | The method (`GET`, `POST`, …), or `GET /users/:id` once a `node:http` route is known; `HTTP` for a nonstandard method |
| Parent | The caller's `traceparent`, when valid; otherwise a new trace |
| Status | `ERROR` for a 5xx response, an abandoned response, or a reset connection |

| Attribute | When |
| --- | --- |
| `http.request.method` | Always. Methods outside the standard nine are `_OTHER`. |
| `http.request.method_original` | For `_OTHER` |
| `url.scheme` | Always |
| `url.path` | Always |
| `url.query` | When the target has a query string. The values of `AWSAccessKeyId`, `Signature`, `sig`, `X-Goog-Signature`, `X-Amz-Signature`, `X-Amz-Credential`, and `X-Amz-Security-Token` are redacted; other values are exported as sent, so keep secrets out of URLs. |
| `network.protocol.version` | Always: `1.0`, `1.1`, or `2` |
| `server.address`, `server.port` | From the `Host` header |
| `user_agent.original` | When sent |
| `client.address`, `network.peer.address`, `network.peer.port` | When the peer is known |
| `http.response.status_code` | When a response was sent |
| `http.request.body.size`, `http.response.body.size` | When nonzero or known |
| `http.route` | `node:http` servers, when set by the handler or a router; see [Routes](https://elide.help/docs/opentelemetry/runtime#routes) |
| `error.type` | `abandoned`, `reset`, or the 5xx status code |
| `baggage.<key>` | For keys listed in `propagation.baggage.spanAttributes` |

A handler that throws adds an `exception` event to the server span and sets its status to `ERROR`.

### HTTP client spans

One `CLIENT` span per outgoing `fetch` request, when `runtime.traces` is on.

| Field | Value |
| --- | --- |
| Name | The method: `GET`, `POST`, … |
| Parent | The span active where `fetch` was called |
| Status | `ERROR` for a 4xx or 5xx response, or a failed request |

| Attribute | When |
| --- | --- |
| `http.request.method`, `http.request.method_original` | As for server spans |
| `url.full` | Always |
| `server.address`, `server.port` | From the URL |
| `http.response.status_code` | When a response arrived |
| `network.protocol.version` | When known |
| `http.response.body.size` | When known |
| `error.type` | The 4xx or 5xx status code, or the failure kind |

### Toolchain

Recorded by `elide build`, `elide test`, and `elide install`. The root span continues
a `TRACEPARENT` (and `TRACESTATE`) from the environment.

| Span | Parent | Attributes |
| --- | --- | --- |
| `elide.build`, `elide.test`, `elide.install` | `TRACEPARENT`, or a new trace | `elide.command`, `process.exit.code`, `elide.ci.*`; `ERROR` status for a nonzero exit code |
| `elide.build.<stage>` | Command span | `elide.build.category` |
| `elide.task <name>` | Command span | `elide.build.category`, `elide.build.task`, `elide.build.outcome`, `elide.build.cache_hit` |
| `elide.task.<operation>` | Task span | same as tasks |
| `elide.test.file <path>`, `elide.test.fork <source-set>` | Task span | `elide.test.execution.*`, `elide.test.shard.index/count` for JS files, `elide.test.count.<status>`, `process.exit.code` |
| test name | Execution span (command for records without an execution) | `elide.test.engine`, `elide.test.name`, `elide.test.status`, `elide.test.suite`, `elide.test.file`, `elide.test.source_set`, `elide.test.failure`; `ERROR` status on failure |

Report attributes (schema version 1):

| Attribute | Meaning |
| --- | --- |
| `elide.report.schema_version` | Integer `1` on every report span |
| `elide.invocation.id` | `<trace-id>/<command-span-id>`, shared by all spans in one invocation |
| `elide.span.type` | `command`, `build_stage`, `task`, `task_operation`, `test_execution`, or `test` |
| `elide.project.name`, `elide.workspace.name` | Manifest identities where available; member tasks override project name |
| `elide.task.path`, `elide.task.type` | Workspace-qualified task key and implementation class; type may change across releases |
| `elide.task.dependencies` | Array of scheduled prerequisite task paths; edges are not parent-child span relationships |
| `elide.task.cacheable` | Whether the task permits caching; separate from an actual cache hit |
| `elide.build.workers/peak_ready/ready_wait_ns/critical_path_ns/critical_path` | Command summary: worker count, peak ready tasks, aggregate ready wait, critical-path duration and ordered task paths |
| `elide.build.operation`, `thread.name` | Driver region name and the thread that completed it |
| `elide.test.execution.id/kind/path` | Execution span ID, `file` or `fork`, and project-relative file or source-set name |
| `elide.test.shard.index/count` | Zero-based JS file index and discovered file count; indexes are local to the task |
| `elide.test.count.<status>` | Counts for observed `pass`, `fail`, `skip`, `todo`, `abort`, and `filtered` results; absent means zero |
| `elide.test.id` | Length-prefixed engine, nullable file, nullable source set, suite components, and test name; null encodes as `-1:`, strings as `<UTF-16-length>:<value>` |
| `elide.test.identity.kind` | `display_path`; not a native engine unique ID |
| `elide.test.occurrence` | One-based occurrence of that identity within the execution; not a retry counter |
| `elide.test.timing.kind`, `elide.test.duration_ns` | `result_duration` and the runner's original duration in nanoseconds |

For cross-build test comparison, use repository/service scope, workspace/project, task path, and `elide.test.id`.
For one emitted result, use invocation ID plus execution ID plus test ID plus occurrence (or its OTLP span ID).
Test children carry their execution identity and task path explicitly, so queries do not require walking the tree.
See [Builds and tests in CI](https://elide.help/docs/opentelemetry/runtime#builds-and-tests-in-ci) for timing and completeness limits.

CI attributes come from the provider's own variables:

| Attribute | GitHub Actions | GitLab | Buildkite | CircleCI |
| --- | --- | --- | --- | --- |
| `elide.ci.provider` | `github` | `gitlab` | `buildkite` | `circleci` |
| `elide.ci.pipeline.name` | `GITHUB_WORKFLOW` | `CI_PIPELINE_NAME` | `BUILDKITE_PIPELINE_SLUG` | — |
| `elide.ci.pipeline.run.id` | `GITHUB_RUN_ID` | `CI_PIPELINE_ID` | `BUILDKITE_BUILD_ID` | `CIRCLE_WORKFLOW_ID` |
| `elide.ci.pipeline.run.attempt` | `GITHUB_RUN_ATTEMPT` | — | — | — |
| `elide.ci.pipeline.run.url` | built from `GITHUB_SERVER_URL` | `CI_PIPELINE_URL` | `BUILDKITE_BUILD_URL` | `CIRCLE_BUILD_URL` |
| `elide.ci.job.name` | `GITHUB_JOB` | `CI_JOB_NAME` | `BUILDKITE_LABEL` | `CIRCLE_JOB` |
| `elide.ci.vcs.ref` | `GITHUB_REF_NAME` | `CI_COMMIT_REF_NAME` | `BUILDKITE_BRANCH` | `CIRCLE_BRANCH` |
| `elide.ci.vcs.revision` | `GITHUB_SHA` | `CI_COMMIT_SHA` | `BUILDKITE_COMMIT` | `CIRCLE_SHA1` |
| `elide.ci.vcs.repository` | `GITHUB_REPOSITORY` | `CI_PROJECT_PATH` | — | — |

Any other CI that sets `CI` gets `elide.ci.provider = generic`.

### Span limits

A span still open when the process exits is ended and exported with `elide.span.reclaimed = true`. So is the oldest
open span once more than 16,384 are open at the same time.

## Metrics

Runtime metrics are recorded when `runtime.metrics` is on and some destination accepts metrics.

| Instrument | Kind | Unit | Attributes |
| --- | --- | --- | --- |
| `http.server.request.duration` | Histogram | `s` | `http.request.method`, `url.scheme`, `network.protocol.version`, `http.response.status_code`, `error.type` |
| `http.server.active_requests` | UpDownCounter | `{request}` | `http.request.method`, `url.scheme` |
| `http.server.request.body.size` | Histogram | `By` | Same dimensions as server duration, including a known `http.route` |
| `http.server.response.body.size` | Histogram | `By` | Same dimensions as server duration, including a known `http.route` |
| `elide.http.server.rejected_requests` | Counter | `{request}` | `http.response.status_code` |
| `http.client.request.duration` | Histogram | `s` | `http.request.method`, `server.address`, `server.port`, `url.scheme`, `network.protocol.version`, `http.response.status_code`, `error.type` |
| `process.cpu.time` | Counter (observable) | `s` | `cpu.mode` = `user`, `system` |
| `process.memory.usage` | UpDownCounter (observable) | `By` | — |
| `process.thread.count` | UpDownCounter (observable) | `{thread}` | — |
| `process.unix.file_descriptor.count` | UpDownCounter (observable) | `{file_descriptor}` | — |
| `process.uptime` | Gauge (observable) | `s` | — |
| `elide.errors` | Counter | `{error}` | `error.type`, `elide.error.origin` |

Process metrics are collected on Linux, macOS, and Windows. `elide.http.server.rejected_requests` counts requests Elide
answered itself because they were malformed or unsupported; they never reach your handler.

HTTP duration histograms use the bucket boundaries `0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5,
5, 7.5, 10` seconds and carry exemplars of their request span.

`elide.error.origin` values:

| Value | Recorded for |
| --- | --- |
| `uncaught` | An uncaught exception |
| `rejection` | An unhandled promise rejection |
| `handler` | A request handler that threw or rejected |
| `crash` | A crash of an earlier process, reported by the next one |
| `guest_api` | `span.recordException(…)` in your code |

## Log records

| Record | Scope | Severity | Attributes |
| --- | --- | --- | --- |
| Error | `dev.elide.runtime`; the span's scope for `recordException` | `ERROR` | `event.name = exception`, `exception.type`, `exception.message`, `exception.stacktrace`, `elide.error.origin` |
| Crash report | `dev.elide.runtime` | `FATAL` | as above, plus `elide.crash.origin`, `elide.crash.file`, `elide.crash.signature`, `elide.crash.exit_code`, `elide.crash.subcommand`, `elide.crash.build_id`, `process.pid` |
| Elide's own log | `dev.elide.runtime` | at or above `runtime.logs` | `elide.log.target`, `code.file.path`, `code.line.number` |
| `console.*` | `console` | `console.trace` TRACE, `debug` DEBUG, `log` and `info` INFO, `warn` WARN, `error` and `assert` ERROR | — |
| Python `logging` | the logger's name | mapped from the record level | `code.file.path`, `code.function.name`, `code.line.number`, and `exception.*` with `exc_info` |

Every record emitted inside a span carries its trace id, span id, and trace flags.

## Profiles

Experimental. One OTLP CPU profile per export (every 30 seconds and at exit) in the `dev.elide.runtime.profiler`
scope, with `samples`/`count` values and a `cpu`/`nanoseconds` period. Each sample links to the span that was active
on its thread. See [Profiles](https://elide.help/docs/opentelemetry/signals#profiles).

## Not yet recorded

- Garbage collection and event-loop delay metrics.
- Client spans and `traceparent` injection for `node:http` client requests and Python HTTP clients.
- Spans for engine start, module loading, or worker threads.
