e-volv
Docs menu

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

shell
npm install @evolve/logs
typescript
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

typescript
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.

text
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

typescript
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:

typescript
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

typescript
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):

typescript
import { EvolveLogsCallbackHandler } from '@evolve/logs/langchain';

const result = await chain.invoke({ question }, {
  callbacks: [new EvolveLogsCallbackHandler()],
});

Vercel AI SDK (needs ai, v4+):

typescript
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

typescript
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

typescript
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

OptionDefaultMeaning
key, urlIngest key and endpoint; both required to send.
serviceService name shown in Observer; the project decides ownership, this labels the events.
environment, releaseSent as deployment.environment and service.release on every event.
redactKeys[]Extra attribute keys to redact, merged into the built-in list.
captureConsolefalseAlso emit console.* calls as events.
captureUncaughttrueuncaughtException and unhandledRejection become error events, then re-emit so the process behaves as before.
sampleRate1Keep rate between 0 and 1.
fetchImplglobal fetchBring your own fetch for tests or custom agents.

Entry points

ImportRuns onGives you
@evolve/logsNode.js 18+init, log, span, withTrace, runWithTraceparent, the pino and winston transports.
@evolve/logs/edgeCloudflare Workers, Vercel Edge, Deno DeploycreateEdgeClient: fetch-only batching with flushOnEnd(ctx) for waitUntil.
@evolve/logs/langchainNode.js, with @langchain/coreEvolveLogsCallbackHandler: one span per chain, node, tool and model call.
@evolve/logs/aiNode.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.