Node.js core: @trailscope/node
The client the framework integrations are built on. Use it directly in workers, scripts and frameworks without an integration; for web apps use @trailscope/express, @trailscope/fastify or @trailscope/nestjs. Shared behaviour is described in sdk-spec.md.
npm install @trailscope/nodeimport { init } from '@trailscope/node';
const trailscope = init({ service: 'report-worker' }); // TRAILSCOPE_INGEST_KEY from the environment
await trailscope.trackJob('report.build', () => buildReport(), { attempt: 1 });trailscope.log('info', 'nightly run finished');process.on('SIGTERM', () => void trailscope.shutdown());init() creates the process-wide client that the integrations pick up; new TrailscopeClient(options) creates an independent one.
Options
Section titled “Options”| Option | Env | Default | |
|---|---|---|---|
endpoint |
TRAILSCOPE_ENDPOINT |
http://localhost:8080 |
|
ingestKey |
TRAILSCOPE_INGEST_KEY |
Without one nothing is sent | |
service |
TRAILSCOPE_SERVICE |
unknown-service |
|
environment |
TRAILSCOPE_ENVIRONMENT, NODE_ENV |
production |
|
release |
TRAILSCOPE_RELEASE |
||
enabled |
TRAILSCOPE_ENABLED=0 disables |
true |
When false every call is a no-op |
maxQueueSize |
10000 |
Oldest dropped beyond this | |
flushIntervalMs / flushAt / maxBatchSize |
1000 / 500 / 1000 |
||
requestTimeoutMs / shutdownTimeoutMs |
10000 / 5000 |
||
sampleRate / routeSampleRates |
1 / {} |
Errors and slow requests are always kept | |
slowMs |
1000 |
||
captureBodies |
'errors' |
'always', 'never' |
|
maxBodyBytes |
8192 |
Per body | |
ignoreRoutes |
health and readiness routes | Strings (exact) or regexes | |
requestIdHeader |
x-request-id |
||
redact |
{ headers, keys, patterns, maskIp: true, disableDefaults: false } |
Added to the built-in rules | |
remoteConfig |
true (false with a custom fetch) |
Load sampling and capture settings from /v1/config |
|
beforeSend |
Edit an item or return null to drop it; runs in the flush |
||
debug |
TRAILSCOPE_DEBUG=1 |
false |
true or a function receiving diagnostics |
fetch |
globalThis.fetch |
Sampling and capture options set here win over the project settings from the server.
// Context of the current request (no-ops outside one)setUser(userId); setCorrelation('account_id', id); setAttribute('plan', 'pro');recordError(err); // attach the exception that failed the requestcurrentContext(); runWithContext(ctx, fn);
// Events, logs and spansclient.captureEvent({ kind: 'custom', name: 'export.created', attributes: { rows: 1200 } });client.log('warn', 'cache miss storm', { attributes: { keyspace: 'sessions' }, error });client.captureException(err);await client.span('external', 'POST billing-provider/charges', () => fetch(url, init));
// Jobs: a job started inside a request belongs to itawait client.trackJob('email.send', () => send(), { attempt: 2 });const carried = serializeContext(); // store with the job...await client.trackJob('email.send', () => send(), { context: deserializeContext(carried) }); // ...restore in the worker
// Lifecycleawait client.flush(); await client.shutdown(); client.stats();Instrumenting another HTTP framework
Section titled “Instrumenting another HTTP framework”observeHttp(client, req, res, next, { route, body, ip }) instruments a Node.js IncomingMessage/ServerResponse pair: it builds the context, echoes the request ID, keeps the start of the response body, runs next inside the context and records the request when the response closes. Frameworks that don’t expose the Node objects can call client.contextFromHeaders() at the start and client.recordHttp(ctx, exchange) at the end, as @trailscope/fastify does.