Initial Commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user