<!-- 0Gkit docs — @foundryprotocol/0gkit-observability
     Source: https://docs.0gkit.com/packages/0gkit-observability
     LLM-friendly Markdown twin of the page. -->

# @foundryprotocol/0gkit-observability

> OpenTelemetry instrumentation for 0gkit primitives. One call wires every
> Storage / Compute / DA / Attestation operation as an OTel span with `0gkit.*`
> semantic attributes. Bundle ≤ 20 KB gzipped.

## What it does

`instrument0g()` patches the public methods of `Storage`, `Compute`, and `DA`
at runtime so every call emits an OTel span. The span name is
`0gkit.<primitive>.<method>` (e.g. `0gkit.storage.upload`). Attributes
include `0gkit.network`, `0gkit.size_bytes`, `0gkit.gas_native`,
`0gkit.fee_native`, `0gkit.confirm_seconds`, `0gkit.root`, and so on.
Failures record the exception, set the span status to ERROR, and attach
`0gkit.error_code` (the SCREAMING_SNAKE code from the
[error taxonomy](/errors)).

## When to use it

- Production apps that need to know what's slow, what's expensive, and what's
  failing in their 0G calls.
- Cost-attribution dashboards: `0gkit.gas_native` + `0gkit.fee_native`
  attributes feed straight into per-team / per-feature cost rollups.
- Anywhere you already have an OTel collector (Honeycomb, Datadog, Tempo,
  Vercel OTel, Grafana Cloud).

## Install

```bash
pnpm add @foundryprotocol/0gkit-observability @opentelemetry/api

# Only if you don't already have an OTel SDK configured:
pnpm add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
```

## Quickstart — auto SDK setup

```ts
import { instrument0g } from "@foundryprotocol/0gkit-observability";

// At process boot, BEFORE any Storage / Compute / DA call:
await instrument0g({
  serviceName: "my-app",
  exporter: {
    kind: "otlp",
    endpoint: "https://api.honeycomb.io/v1/traces",
    headers: { "x-honeycomb-team": process.env.HONEYCOMB_API_KEY! },
  },
});

// From here, every Storage / Compute / DA call emits a span.
// Nothing else in your app needs to change.
```

## Bring your own SDK (`mode: "attach"`)

If you already configure `@opentelemetry/sdk-node` (or any other OTel SDK) in
your app, pass `mode: "attach"` so `instrument0g` skips SDK setup and only
patches primitives:

```ts
import { instrument0g } from "@foundryprotocol/0gkit-observability";

await instrument0g({ mode: "attach" });
```

## Tests — patch fakes with explicit targets

Tests can patch fake classes by passing `targets` explicitly. This bypasses
the dynamic imports of the real primitives, so it's also synchronous in
practice (the `await` resolves immediately):

```ts
import { instrument0g, disinstrument0g } from "@foundryprotocol/0gkit-observability";

class FakeStorage {
  network = "galileo";
  async upload() {
    return { root: "0xabc" };
  }
}

await instrument0g({
  mode: "attach",
  targets: { storage: { class: FakeStorage, methods: ["upload"] } },
});

// Run tests against an in-memory exporter.

disinstrument0g(); // restores originals
```

## Span attribute reference

All attribute keys live in a single frozen `ATTR` constant. Every key uses
the `0gkit.*` vendor namespace so collectors and cost calculators can filter
on the prefix.

| Constant               | Key                     | Notes                                                 |
| ---------------------- | ----------------------- | ----------------------------------------------------- |
| `ATTR.NETWORK`         | `0gkit.network`         | `"galileo"` / `"aristotle"` / `"local"`               |
| `ATTR.OP`              | `0gkit.op`              | `"storage.upload"`, `"compute.inference"`, …          |
| `ATTR.SIZE_BYTES`      | `0gkit.size_bytes`      | Bytes uploaded / downloaded / published               |
| `ATTR.SEGMENTS`        | `0gkit.segments`        | Storage segments (256 KiB chunks)                     |
| `ATTR.GAS_NATIVE`      | `0gkit.gas_native`      | Gas units (stringified bigint)                        |
| `ATTR.FEE_NATIVE`      | `0gkit.fee_native`      | Native wei fee (stringified bigint)                   |
| `ATTR.CONFIRM_SECONDS` | `0gkit.confirm_seconds` | Confirmation latency in seconds                       |
| `ATTR.ROOT`            | `0gkit.root`            | Storage Merkle root                                   |
| `ATTR.TX_HASH`         | `0gkit.tx_hash`         | Transaction hash                                      |
| `ATTR.BLOCK_NUMBER`    | `0gkit.block_number`    | Block number                                          |
| `ATTR.MODEL`           | `0gkit.model`           | Compute model id                                      |
| `ATTR.INPUT_TOKENS`    | `0gkit.input_tokens`    | Input tokens consumed                                 |
| `ATTR.OUTPUT_TOKENS`   | `0gkit.output_tokens`   | Output tokens generated (or max for estimates)        |
| `ATTR.ERROR_CODE`      | `0gkit.error_code`      | SCREAMING_SNAKE code from the error taxonomy          |
| `ATTR.DRY_RUN`         | `0gkit.dry_run`         | `true` for `.estimate()` and `{ dryRun: true }` calls |

Standard OTel `http.*` / `rpc.*` attributes are layered on top by user
instrumentation — we don't duplicate them.

## What's wrapped today

| Primitive     | Methods                                         |
| ------------- | ----------------------------------------------- |
| `Storage`     | `upload`, `download`, `estimate`, `exists`      |
| `Compute`     | `inference`, `estimate`                         |
| `DA`          | `publish`, `estimate`                           |
| `Attestation` | (free functions — pass an explicit target only) |

`Attestation` is currently a set of free functions, not a class. The default
target set skips it; if you wire your own attestation client class, pass
`targets.attestation` to instrument it. See
[`docs/DECISIONS.md` D32](https://github.com/rajkaria/0gkit/blob/main/docs/DECISIONS.md)
for the rationale.

## Local trace mirror (`OGKIT_TRACE_DIR`)

Set `OGKIT_TRACE_DIR=<path>` in the process that calls `instrument0g(...)`
and every span will be mirrored as one JSON line to
`<path>/<YYYY-MM-DD>-<traceId>.jsonl`, in addition to your configured OTel
exporter. Off by default — pure opt-in, no network, no extra infra.

```bash
export OGKIT_TRACE_DIR=.0gkit/traces
node ./your-handler.js
0g traces list
```

The sink is fire-and-forget: a full disk or permissions error never crashes
the handler. The CLI's [`0g traces`](/cli#0g-traces) subcommand reads these
files back, and `0g traces inspect <id> --json | 0g cost forecast --from-jaeger -`
replays one local trace as a real per-op cost breakdown.

## Bundle size

The public entry bundles to ≤ 20 KB gzipped. Asserted by
`bundle-size.test.ts` on every CI run. `@opentelemetry/api` is externalised
(peer dep); the optional SDK + exporter peers are lazy-imported and never
reach the bundle unless the caller asks for them.

## Exporter recipes

- [Honeycomb](/concepts/observability/exporters/honeycomb)
- [Datadog](/concepts/observability/exporters/datadog)
- [Vercel OTel](/concepts/observability/exporters/vercel)

## See also

- [Concept: Observability](/concepts/observability) — `0gkit.*` attribute
  semantics + cost attribution playbook
- [Error codes](/errors) — every `0gkit.error_code` value links to a fix page
- [Source on GitHub](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-observability)

## Exports

- `type AttrKey`
- `type ExporterConfig`
- `type InstrumentConfig`
- `type InstrumentMode`
- `type InstrumentTargets`
- `type TraceFileEntry`
- `type TraceFileSummary`
- `type TraceRecord`
- `appendSpanRecord`
- `defaultTraceDir`
- `isSinkEnabled`
- `listTraceFiles`
- `pathForTrace`
- `readTraceFile`
- `summarizeTrace`
