first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
# span-utils
## Purpose
Provides ergonomic helper functions for working with OpenTelemetry spans. Rather than writing the same try/catch/finally/recordException/setStatus boilerplate in every traced function, callers use `withSpan` to declaratively wrap work in a span with correct error handling built in.
These utilities depend only on `@opentelemetry/api` and are therefore compatible with any OTel SDK implementation (or the no-op fallback), making them safe to import in library code that should not force a specific SDK on its consumers.
## Features
- `withSpan` — wraps an async function in an active span; records exceptions and ends the span unconditionally
- `recordException` — normalizes any thrown value to an Error and records it with ERROR status
- `addAttributes` — sets a bulk `Attributes` map on a span in a single call
- `getTraceContext` — extracts `traceId`, `spanId`, and `traceFlags` from the currently active span
## Usage: Basic Example
```ts
import { withSpan } from "@suaveplan/opentelemetry/span-utils";
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("my-service");
const result = await withSpan(tracer, "user.fetch", async (span) => {
span.setAttribute("user.id", userId);
return await db.findUser(userId);
});
```
## Usage: Advanced Example
```ts
import { withSpan, addAttributes, getTraceContext } from "@suaveplan/opentelemetry/span-utils";
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("payment-service", "1.0.0");
async function processPayment(orderId: string, amount: number) {
return withSpan(
tracer,
"payment.process",
async (span) => {
addAttributes(span, {
"payment.order_id": orderId,
"payment.amount": amount,
"payment.currency": "USD",
});
const result = await chargeGateway(orderId, amount);
const ctx = getTraceContext();
console.log("trace:", ctx.traceId, "span:", ctx.spanId);
return result;
},
{ attributes: { "payment.initial": true } }
);
}
```
## API Reference
### `withSpan(tracer, name, fn, options?)`
```ts
function withSpan<T>(
tracer: Tracer,
name: string,
fn: (span: Span) => Promise<T>,
options?: { attributes?: Attributes }
): Promise<T>
```
Starts an active child span, calls `fn` with it, ends the span in `finally`, and records any exception + sets ERROR status before re-throwing.
### `recordException(span, error)`
```ts
function recordException(span: Span, error: unknown): void
```
Records `error` on `span` as a span event and sets `SpanStatusCode.ERROR`. Non-Error values are wrapped in `new Error(String(error))` before recording.
### `addAttributes(span, attrs)`
```ts
function addAttributes(span: Span, attrs: Attributes): void
```
Sets each key/value pair from `attrs` on the span. `undefined` values are skipped.
### `getTraceContext()`
```ts
function getTraceContext(): { traceId?: string; spanId?: string; traceFlags?: number }
```
Returns the active span's context fields. All fields are `undefined` when no span is active.
## Implementation Notes
`withSpan` uses `tracer.startActiveSpan` so that child spans created inside `fn` are automatically parented to this span via the context API — no manual context propagation required. The `finally` block ensures `span.end()` is always called even when `fn` throws, which is the single most common OTel correctness bug in hand-written tracing code. `recordException` follows the OTel spec by calling the API's built-in `span.recordException` rather than manually building the event, ensuring the `exception.type`, `exception.message`, and `exception.stacktrace` attributes are populated correctly.