Tracing

Attach trace and span IDs to logs so you can follow one action across services.

What tracing does

While a trace is active, every log the logger sends carries the trace’s traceId and spanId. The Distributed Traces page groups logs by trace ID, so you can read every log from one user action in order.

That is all it does in 1.5.2. Spans are not sent to Apperio as their own records, and no headers are added to your requests automatically.

Turn it on

TypeScript
import { Apperio } from 'apperio';

const logger = new Apperio({
  apiKey: 'your-api-key',
  projectId: 'your-project-id',
  tracing: { enabled: true },
});

With tracing off, startTrace() and createChildSpan() return null.

Start and end a trace

TypeScript
const trace = logger.startTrace('checkout');
// trace: { traceId, spanId, sampled: true }, or null with tracing off

logger.info('Checkout started');   // carries trace.traceId and trace.spanId
logger.info('Payment accepted');   // same IDs

logger.endTrace();
logger.info('Back to browsing');   // no trace IDs

logger.getCurrentTrace() returns the active trace, or null.

Child spans

A span times one step inside a trace. Spans live only in your code: to keep a span’s result, log it.

TypeScript
logger.startTrace('checkout');

const span = logger.createChildSpan('charge-card');
span?.setAttribute('provider', 'example-pay');

try {
  await chargeCard();
  logger.info('Card charged', { span: span?.end('ok') });
} catch (err) {
  logger.error('Card declined', err instanceof Error ? err : undefined, {
    span: span?.end('error'),
  });
}

logger.endTrace();
Span methodWhat it does
setAttribute(key, value)Stores a value on the span.
end(status?)Stops the clock and returns the span’s data: name, IDs, start and end times, duration in milliseconds, status (ok or error) and attributes.
getData()Returns the span’s data without ending it.

Start a trace first

With tracing on but no active trace, createChildSpan() throws Cannot create child span: no active trace. Logs keep the trace’s own span ID while a child span is open.

Across services

To follow an action from the browser to your API, send the trace ID along with the request. TracePropagator writes and reads the headers X-Trace-ID, X-Span-ID and X-Parent-Span-ID:

browser.ts
import { TracePropagator } from 'apperio';

const trace = logger.startTrace('checkout');
const headers = trace
  ? TracePropagator.inject({ 'Content-Type': 'application/json' }, trace)
  : { 'Content-Type': 'application/json' };

await fetch('/api/orders', { method: 'POST', headers, body: '{}' });
server.ts
import { TracePropagator } from 'apperio';

export function handleOrder(headers: Record<string, string>) {
  const incoming = TracePropagator.extract(headers); // null if the headers are missing
  logger.info('Order received', { traceId: incoming?.traceId });
}

The receiving side can’t continue the same trace: startTrace() always creates new IDs. Log the incoming ID in your data, as above, and search for it.

Limits

  • One trace at a time per logger. On a server handling many requests at once, logs from other requests pick up the same IDs, so use tracing where one action runs at a time: a browser flow, a job or a script.
  • Trace IDs are random UUIDs, not W3C traceparent values.