Node.js and TypeScript
@evolve/logs is the SDK for anything that runs on Node.js 18 or later: an Express or NestJS API, a Fastify service, a BullMQ worker, the server side of a Next.js app. It ships as CommonJS with types. It uses AsyncLocalStorage for trace context, zlib for gzip and process hooks for flush-on-exit, which is why the main entry is a server package. The same package carries an edge entry for Workers-style isolates and two agent integrations (see entry points); the browser entry is in development.
Install and initialise
npm install @evolve/logs
import { init, log, span, withTrace } from '@evolve/logs';
init({
key: process.env.EVOLVE_LOGS_KEY, // the project's ingest key
url: 'https://api.e-volv.io/api/public/v1/logs',
service: 'orders-api',
environment: process.env.NODE_ENV,
release: process.env.GIT_SHA,
});Call init() once, before the first request. Without a key and URL the SDK is a no-op and warns once; it never throws into your code.
Logs
log.info('order created', { orderId: 'o_1', total: 42.5 });
log.warn('retrying payment', { orderId: 'o_1', attempt: 2 });
log.error('payment failed', { orderId: 'o_1' });Six levels: trace, debug, info, warn, error, fatal. Attributes are a flat object; nested objects are stringified. Events inside a trace carry its ids automatically.
Errors
Pass the Error object, not its message. The SDK sends exception.type, exception.message and exception.stack, which is what turns the event into an error occurrence with a stack on the group page.
try {
await charge(order);
} catch (err) {
log.exception(err, { orderId: order.id });
throw err;
}Bundled services (webpack, esbuild) must ship their source map and start Node with --enable-source-maps, otherwise every frame reads dist/main.js:41447 and the group cannot name the file that raised it.
Traces and spans
await withTrace(async () => {
// everything inside shares one traceId
await span('db.query', { table: 'orders' }).run(() => db.query('...'));
await fetch('https://internal/inventory'); // traceparent header injected
});withTrace(fn) starts a root trace. span(name, attrs?) returns a handle with traceId, spanId, run(fn) (ends the span when fn settles, failed if it threw) and end(err?) (idempotent). Global fetch is patched once at init() to inject traceparent inside a trace; an existing header is never overwritten. traceparent() returns the current value for manual propagation.
Inbound requests
Continue a caller’s trace instead of starting a new one:
import { runWithTraceparent } from '@evolve/logs';
app.use((req, _res, next) => {
runWithTraceparent(req.headers.traceparent as string | undefined, () => {
next(); // handlers run inside the caller's trace, or a fresh one
});
});Queue hops
import { runWithTraceparent, span, traceparent } from '@evolve/logs';
// producer
await queue.add('work', { payload, trace: traceparent() });
// consumer: a new hop of the producer's trace
runWithTraceparent(job.data.trace, () =>
span('queue.work', { queue: 'work' }).run(() => handle(job.data.payload))
);Agent integrations
Two optional integrations emit the same span names as the Python SDK’s LangChain handler, so a JavaScript agent’s tool and model calls appear inside the run’s node on the trace graph:graph.<node>, chain.<name>, tool.<name> and llm.<model>. Both are optional peer dependencies; token counts ride on the span as attrs and an error ends the span as failed.
LangChain.js / LangGraph (needs @langchain/core):
import { EvolveLogsCallbackHandler } from '@evolve/logs/langchain';
const result = await chain.invoke({ question }, {
callbacks: [new EvolveLogsCallbackHandler()],
});Vercel AI SDK (needs ai, v4+):
import { wrapLanguageModel } from 'ai';
import { evolveLogsMiddleware } from '@evolve/logs/ai';
const model = wrapLanguageModel({
model: openai('gpt-4o'),
middleware: evolveLogsMiddleware(),
});
// generateText / streamText: every call is an llm.<model> span,
// tool calls inside it are tool.<name> children
await generateText({ model, tools, prompt: '...' });Frameworks
Express
app.use((req, _res, next) => {
withTrace(() => {
log.info('request', { method: req.method, path: req.path });
next();
});
});NestJS
Implement LoggerService on top of the SDK so that Logger.error(message, stack, context) becomes an event with exception.* attributes, and install it with app.useLogger(). Add a middleware that calls runWithTraceparent for inbound requests. Evolve’s own backend does exactly this; the reference implementation is log-transport.ts.
pino and winston
import { createPinoTransport, createWinstonTransport } from '@evolve/logs';
const logger = pino(createPinoTransport());
const wlogger = winston.createLogger({ transports: [createWinstonTransport()] });Levels map to OpenTelemetry severities and metadata becomes attributes; the existing logger stays in place. Both are optional peer dependencies.
Next.js server side
Call init() from instrumentation.ts and report request errors from onRequestError with log.exception. Route handlers, server components and server actions then reach Observer with a stack. Browser errors need the browser entry.
Options
| Option | Default | Meaning |
|---|---|---|
key, url | — | Ingest key and endpoint; both required to send. |
service | — | Service name shown in Observer; the project decides ownership, this labels the events. |
environment, release | — | Sent as deployment.environment and service.release on every event. |
redactKeys | [] | Extra attribute keys to redact, merged into the built-in list. |
captureConsole | false | Also emit console.* calls as events. |
captureUncaught | true | uncaughtException and unhandledRejection become error events, then re-emit so the process behaves as before. |
sampleRate | 1 | Keep rate between 0 and 1. |
fetchImpl | global fetch | Bring your own fetch for tests or custom agents. |
Entry points
| Import | Runs on | Gives you |
|---|---|---|
@evolve/logs | Node.js 18+ | init, log, span, withTrace, runWithTraceparent, the pino and winston transports. |
@evolve/logs/edge | Cloudflare Workers, Vercel Edge, Deno Deploy | createEdgeClient: fetch-only batching with flushOnEnd(ctx) for waitUntil. |
@evolve/logs/langchain | Node.js, with @langchain/core | EvolveLogsCallbackHandler: one span per chain, node, tool and model call. |
@evolve/logs/ai | Node.js, with ai (Vercel AI SDK) | evolveLogsMiddleware: an llm.<model> span per call with tool.<name> children. |
One package, one version. The integrations declare their frameworks as optional peer dependencies, so installing @evolve/logs pulls in neither LangChain nor the AI SDK.
Delivery
Batches flush at 200 events, 2 seconds or 512 KB, gzipped. A 429 is retried after Retry-After or exponential backoff, up to three times; a 413 halves the batch. When the pending buffer exceeds twice the batch size the oldest events are dropped and counted on client.dropped. A beforeExit hook flushes what is pending; call await client.flush() before a hard exit.
Package reference: packages/logs-js. Back to SDK overview.