@jstverify/tracing
v0.7.0
Published
JstVerify Node.js Tracing SDK — distributed tracing for Node.js backends
Downloads
774
Readme
@jstverify/tracing
Node.js distributed tracing SDK for JstVerify application mapping. Auto-instruments your backend to produce trace spans that connect with the JstVerify JavaScript SDK, giving you a full frontend-to-backend service map.
Zero runtime dependencies. Supports Node.js 18+.
Installation
npm install @jstverify/tracingQuick Start
1. Initialize (once at startup)
import { init } from "@jstverify/tracing";
init({
apiKey: "your-sdk-key",
serviceName: "my-backend",
});The endpoint defaults to the production ingestion URL (
https://sdkapi.jstverify.com/v1/tracing/spans). Override for dev environments:init({ apiKey: "your-sdk-key", endpoint: "https://sdkapi.dev.jstverify.com/v1/tracing/spans", serviceName: "my-backend", });
2. Add Framework Middleware
Express:
import { tracingMiddleware } from "@jstverify/tracing";
app.use(tracingMiddleware({ gatewayName: "my-api-gateway" }));Fastify:
import { fastifyTracingPlugin } from "@jstverify/tracing";
fastify.register(fastifyTracingPlugin, { gatewayName: "my-api-gateway" });Koa:
import { koaTracingMiddleware } from "@jstverify/tracing";
app.use(koaTracingMiddleware({ gatewayName: "my-api-gateway" }));Hapi:
import { hapiTracingPlugin } from "@jstverify/tracing";
await server.register({ plugin: hapiTracingPlugin, options: { gatewayName: "my-api-gateway" } });AWS Lambda (API Gateway):
import { JstVerifyTracingMiddleware } from "@jstverify/tracing";
export const handler = JstVerifyTracingMiddleware(async (event, context) => {
return { statusCode: 200, body: "ok" };
});For backend event sources (SQS, SNS, EventBridge):
export const handler = JstVerifyTracingMiddleware({ backendTrace: true })(
async (event, context) => {
// process SQS messages, SNS notifications, etc.
},
);Express / Fastify on AWS Lambda (via @vendia/serverless-express, etc.):
When running a framework app on Lambda, use the framework middleware above for tracing — but you must also call flush() at the end of each request. The background flush timer uses setInterval.unref() which won't fire between Lambda invocations.
import { flush } from "@jstverify/tracing";
app.get("/api/example", async (req, res) => {
try {
res.json({ ok: true });
} finally {
await flush();
}
});Note: The
JstVerifyTracingMiddlewareLambda wrapper handles flushing automatically — this manual flush is only needed when using framework middleware on Lambda.
AWS AppSync Lambda Resolver:
import { JstVerifyAppSyncMiddleware } from "@jstverify/tracing";
export const handler = JstVerifyAppSyncMiddleware(async (event, context) => {
return [{ id: "1", name: "Alice" }];
});The AppSync middleware extracts trace context from event.request.headers or event.stash (pipeline resolvers) and derives the operation name from event.info.parentTypeName and event.info.fieldName (e.g. Query.listUsers). Only direct transport mode is supported — relay mode is not available for AppSync since GraphQL responses cannot carry custom HTTP headers.
3. Manual Instrumentation (optional)
import { trace, traceSpan, SpanHandle } from "@jstverify/tracing";
// Decorator-style wrapping
const processPayment = trace("process-payment")(async (orderId: string) => {
// ...
});
// Callback-based spans
async function handleOrder(orderId: string) {
const result = await traceSpan("validate-order", async (span: SpanHandle) => {
// ...
span.setStatus(200);
return validated;
});
await traceSpan("charge-card", async (span: SpanHandle) => {
// ...
span.setHttpMetadata({ method: "POST", url: "/payments/charge", statusCode: 201 });
});
}4. Custom Attributes
Attach custom key-value metadata to any span. Values are coerced to strings.
Via trace() options:
const processPayment = trace("process-payment", {
attributes: { orderId: "ORD-123", amount: 49.99, express: true },
})(async (orderId: string) => {
// ...
});Via SpanHandle.setAttribute():
await traceSpan("validate-order", async (span: SpanHandle) => {
span.setAttribute("orderId", orderId);
span.setAttribute("itemCount", items.length);
// ...
});When both options and handle set the same key, the handle value wins.
5. DynamoDB Tracing
Use the traceDynamodb() helper to wrap individual DynamoDB operations:
import { traceDynamodb } from "@jstverify/tracing";
// Wraps any async operation with a DynamoDB-typed span
const result = await traceDynamodb("GetItem", "users-table", async () => {
return client.send(new GetItemCommand({ TableName: "users-table", Key: { id: { S: "123" } } }));
});
const items = await traceDynamodb("Query", "orders-table", async () => {
return client.send(new QueryCommand({ TableName: "orders-table", ... }));
});Each call creates a child span with the operation name (e.g. GetItem users-table) and serviceType: "dynamodb".
6. Auto-Patching
Outgoing HTTP (fetch):
import { patchFetch } from "@jstverify/tracing";
patchFetch();
// All subsequent fetch() calls are automatically traced with headers injectedAWS SDK v3:
import { createAwsSdkTracingMiddleware } from "@jstverify/tracing";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
client.middlewareStack.use(createAwsSdkTracingMiddleware());7. Trace Context Propagation
For SQS and EventBridge, use the propagation helpers to forward trace context:
import { propagation } from "@jstverify/tracing";
// SQS — add as MessageAttributes
const attrs = propagation.sqsMessageAttributes();
await sqs.send(new SendMessageCommand({
QueueUrl: "...",
MessageBody: "...",
MessageAttributes: attrs,
}));
// EventBridge — merge into detail
const traceCtx = propagation.eventbridgeTraceContext();
await events.send(new PutEventsCommand({
Entries: [{ Source: "my-app", DetailType: "OrderPlaced", Detail: JSON.stringify({ ...payload, ...traceCtx }) }],
}));8. Shutdown
import { shutdown } from "@jstverify/tracing";
await shutdown();Shutdown is also registered via process.on('beforeExit') automatically.
Configuration Options
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| apiKey | string | required | Your JstVerify SDK API key |
| endpoint | string | production URL | Span ingestion endpoint URL (defaults to https://sdkapi.jstverify.com/v1/tracing/spans) |
| serviceName | string | required | Service name shown in the service map |
| serviceType | string | "http" | Service type identifier (auto-detects "lambda" in Lambda environments) |
| gatewayName | string | — | If set, framework middleware synthesizes an API Gateway parent span |
| transport | string | "direct" | "direct" sends spans via HTTP; "relay" encodes spans into response headers |
| flushInterval | number | 5 | Seconds between background flushes |
| maxQueueSize | number | 200 | Max buffered spans (circular buffer) |
| maxBatchSize | number | 50 | Max spans per API request |
| debug | boolean | false | Enable debug logging |
| sanitizePii | boolean | true | Scrub PII (SSN, email, phone, credit card) from span data |
| piiPatterns | string[] | [] | Additional regex patterns for PII scrubbing |
| piiRedaction | string | "[REDACTED]" | Replacement string for redacted PII |
How It Works
Direct Mode (default)
- The middleware reads
X-JstVerify-Trace-IdandX-JstVerify-Parent-Span-Idheaders from incoming requests (injected by the JS SDK). - A root span is created for each request, with nested child spans for
trace()wrapped functions andtraceSpan()callbacks. - If
patchFetch()is called, outgoingfetchcalls are automatically instrumented — trace headers are injected so downstream services can continue the trace. - Spans are buffered in a queue and flushed to the JstVerify API in batches by a background
setIntervaltimer (.unref()'d so it doesn't block process exit).
Relay Mode
For backends without outbound internet access (private VPC, strict firewalls), relay mode encodes spans into the X-JstVerify-Spans response header. The JstVerify JS SDK reads this header and relays the spans to the ingestion API on behalf of the backend.
init({
apiKey: "your-sdk-key",
serviceName: "my-backend",
transport: "relay", // No endpoint needed
});How it works:
- Each request collects spans in a per-request buffer (async-safe via
AsyncLocalStorage). - When the response is sent, all spans are base64url-encoded into the
X-JstVerify-Spansheader. - The JS SDK decodes the header and merges the spans into its own flush queue.
Limitations:
- ~20-30 spans per response max due to the 7500-byte header size limit.
- Only works for request-response flows — async background jobs have no response to carry spans.
- Cross-origin requests require the
Access-Control-Expose-Headersheader (set automatically by the middleware).
License
MIT
