# 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`, and `X-Goog-Signature` 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 or stage span | `elide.build.category`, `elide.build.task`, `elide.build.outcome`, `elide.build.cache_hit` |
| `elide.task.<operation>` | Task span | same as tasks |
| test name | `elide.test` | `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 |

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` |
| `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.
