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