Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
# otel/instruments
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the OpenTelemetry instruments emitted from `createQueryExecutedMiddleware`. The middleware is the only production code path that records metrics, so this file is the single source of truth for what the package observes.
Two instruments cover both the success and failure paths of every resolver wrapped in the middleware: a counter partitioned by outcome label, and a duration histogram. Tests assert against a real in-memory `MeterProvider` to satisfy AGENTS.md §13 row 5.
## Features
- `graphql.resolvers_executed_total{outcome}` — counter, label `outcome ∈ "success" | "error"`.
- `graphql.resolver_duration_ms` — histogram in milliseconds.
- Lazy global-provider binding via `metrics.getMeter(...)`, so the meter resolved at module load is whatever provider was installed at the time of the first call.
- `resetGraphQLInstruments()` test hook for re-binding against freshly installed providers.
## Usage: Basic
```ts
import { metrics } from "@opentelemetry/api";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { resetGraphQLInstruments } from "@suaveplan/api-plugin-graphql/otel/instruments";
const provider = new MeterProvider({ readers: [] });
metrics.setGlobalMeterProvider(provider);
resetGraphQLInstruments();
```
## Usage: Advanced
```ts
import { metrics } from "@opentelemetry/api";
import {
AggregationTemporality,
InMemoryMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import {
createQueryExecutedMiddleware,
ResolverComposer,
} from "@suaveplan/api-plugin-graphql";
import { resetGraphQLInstruments } from "@suaveplan/api-plugin-graphql/otel/instruments";
const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000 });
const provider = new MeterProvider({ readers: [reader] });
metrics.setGlobalMeterProvider(provider);
resetGraphQLInstruments();
const map = new ResolverComposer()
.useForType("Query", createQueryExecutedMiddleware())
.addResolver("Query", "ping", () => "pong")
.build();
await map.Query.ping(null, {}, { headers: {}, data: {} }, { fieldName: "ping", parentType: "Query", returnType: "String" });
await provider.forceFlush();
const points = exporter.getMetrics();
// inspect points[0].scopeMetrics[0].metrics
```
## API Reference
| Symbol | Kind | Notes |
|---|---|---|
| `GraphQLInstruments` | interface | `{ resolversExecutedTotal: Counter, resolverDurationMs: Histogram }`. |
| `getGraphQLInstruments()` | function | Returns the cached bundle, lazily binding against the current global `MeterProvider`. |
| `resetGraphQLInstruments()` | function | Drops the cache; the next `getGraphQLInstruments()` rebinds against whatever provider is then installed. |
## Implementation Notes
The module deliberately reads `metrics.getMeter` lazily — calling it at import time would bind against the noop provider that ships in `@opentelemetry/api` before any consumer (or test setup) installs a real one. suaveplan test setup installs a real `MeterProvider` in `tests/setup/polyfills.ts`, then calls `resetGraphQLInstruments()` (or its equivalent through the test fixture) to force the first call inside a test to bind against the real provider.
Instrument naming follows the `<package>.<metric>` convention used across the API tier (`auth.*`, `plugin.*`, `graphql.*`). The `outcome` label space is intentionally small (`"success" | "error"`) — finer partitions belong on span attributes, not on counter labels, to keep cardinality bounded.