Skip to main content

What is a Trace?

A trace represents a single logical operation or flow in your application. It’s the fundamental unit of data in the Mirador platform. Traces capture:
  • The operation name
  • Attributes (key-value metadata)
  • Tags (categorical labels)
  • Events (timestamped milestones)
  • Transaction hints (blockchain correlation)

Creating a Trace

Create a trace using the client.trace() method:
const trace = client.trace({ name: 'UserCheckout' });

Trace Options

Web SDK:
OptionTypeDefaultDescription
namestringundefinedName identifying the trace
traceIdstringundefinedResume an existing trace by ID
includeUserMetabooleantrueInclude browser metadata
maxRetriesnumber2Retry attempts on failure
retryBackoffnumber500Base backoff delay (ms)
autoClosebooleanfalseAutomatically close trace on page unload
autoKeepAlivebooleantrue / falseAuto-start keep-alive timer (true for new traces, false when traceId is set)
const trace = client.trace({
  name: 'CriticalOperation',
  includeUserMeta: true,
  maxRetries: 5
});
Node.js SDK:
OptionTypeDefaultDescription
namestringundefinedName identifying the trace
traceIdstringundefinedResume an existing trace by ID (e.g., from frontend SDK)
captureStackTracebooleantrueCapture stack trace at trace creation
maxRetriesnumber2Retry attempts on failure
retryBackoffnumber500Base backoff delay (ms)
autoKeepAlivebooleantrue / falseAuto-start keep-alive timer (true for new traces, false when traceId is set)
const trace = client.trace({
  name: 'BackendOperation',
  traceId: req.headers['x-mirador-trace-id'], // optional: resume frontend trace
  captureStackTrace: false
});

Trace Lifecycle

1

Create

Call client.trace() to create a new trace builder
2

Build

Add attributes, tags, events, and transaction hints using the fluent API
3

Flush

First flush sends FlushTrace to the gateway
4

Update

Subsequent flushes send FlushTrace with new data

Keep-Alive & Closing Traces

The SDK sends periodic keep-alive pings to the server to maintain trace liveness. The ping interval is configurable at the client level:
const client = new Client('your-api-key', {
  keepAliveIntervalMs: 15000  // Ping every 15 seconds (default: 10000)
});

Smart Defaults

The autoKeepAlive option controls whether the keep-alive timer starts automatically when a trace is created:
  • New traces (autoKeepAlive defaults to true): the timer starts automatically after the first flush.
  • Resumed traces (when traceId is set, autoKeepAlive defaults to false): the timer does not start automatically.
This prevents zombie keep-alive timers — for example, when a backend resumes a frontend trace just to append a few events and close it, there’s no need for the backend to keep pinging.
// New trace — keep-alive starts automatically
const trace = client.trace({ name: 'UserSession' });

// Resumed trace — no keep-alive by default
const resumed = client.trace({
  traceId: req.headers['x-mirador-trace-id'],
  name: 'BackendHandler'
});

// Override the default: force keep-alive on a resumed trace
const resumedWithKeepalive = client.trace({
  traceId: req.headers['x-mirador-trace-id'],
  name: 'LongRunningBackend',
  autoKeepAlive: true
});

Manual Control

You can manually start or stop the keep-alive timer at any time using the startKeepAlive() and stopKeepAlive() methods on the trace. Both methods are idempotent — calling them multiple times is safe.
const trace = client.trace({ name: 'ManualControl', autoKeepAlive: false });

// Start keep-alive manually when needed
trace.startKeepAlive();

// Stop it when the trace is idle
trace.stopKeepAlive();

Resilience

The keep-alive timer is resilient to transient network issues:
  • An in-flight guard prevents overlapping pings if a previous ping hasn’t completed yet.
  • On failure, the SDK performs a single retry before counting the attempt as failed.
  • After 3 consecutive failures, the timer automatically stops to avoid wasting resources.

Max Trace Lifetime

You can bound the maximum lifetime of a trace independently of keep-alive using the maxTraceLifetimeMs client option. When the lifetime is exceeded, the trace is automatically closed.
const client = new Client('your-api-key', {
  maxTraceLifetimeMs: 300000  // Auto-close traces after 5 minutes
});
maxTraceLifetimeMs defaults to 0 (disabled). When set, it applies to all traces created by the client.

Closing Traces

Always close traces when you’re done to clean up resources:
const trace = client.trace({ name: 'UserSession' });

try {
  // ... add events, attributes, etc ...
  await trace.close('Session ended');
} catch (error) {
  trace.addEvent('error', { message: error.message });
  await trace.close('Session failed');
}
Enable auto-close on page unload for browser applications:
const trace = client.trace({
  name: 'UserSession',
  autoClose: true  // Automatically close on page unload
});

The Builder Pattern

The Trace class uses a fluent builder pattern. All methods return this, allowing you to chain calls:
const trace = client.trace({ name: 'SwapExecution' })
  .addAttribute('from', '0xabc...')
  .addAttribute('to', '0xdef...')
  .addTags(['dex', 'swap', 'ethereum'])
  .addEvent('quote_received', { price: '1.5' })
  .addEvent('user_approved');

trace.web3.evm.addTxHint('0x123...', 'ethereum');
trace.web3.safe.addMsgHint('0xabc...', 'ethereum', 'Multisig approval');

Getting the Trace ID

In v2, the trace ID is generated client-side at creation time and is available immediately via getTraceId():
const trace = client.trace({ name: 'MyTrace' });
const traceId = trace.getTraceId(); // Available immediately
console.log('Trace ID:', traceId);

Client-Side Trace ID Generation

In v2, trace IDs are generated client-side at creation time as W3C Tracing Context compatible 32-character hex strings. The ID is available immediately via getTraceId() — no need to wait for the server response.
const trace = client.trace({ name: 'MyTrace' });
const traceId = trace.getTraceId(); // Available immediately

// Pass to backend before any network call
fetch('/api/action', {
  headers: { 'X-Mirador-Trace-Id': traceId }
});
This is especially important for cross-SDK trace sharing — the frontend can generate the ID and pass it to the backend via headers before any network call completes.

Flushing

Both the Web SDK and Node.js SDK automatically batch and send trace data at the end of the current JavaScript tick (microtask). All flushes send idempotent FlushTrace requests. You can also call flush() manually at any time to send pending data immediately:
const trace = client.trace({ name: 'MyTrace' })
  .addAttribute('key', 'value')
  .addEvent('started');

trace.flush();  // Send immediately instead of waiting for microtask
flush() is fire-and-forget — it returns void and maintains strict ordering of requests internally.

Cross-SDK Trace Sharing

You can share a trace between the frontend (Web SDK) and backend (Node.js SDK) so that both sides contribute data to the same trace. This is useful when a user action in the browser triggers a backend API call that you want to correlate.

How It Works

  1. The frontend creates a trace — the ID is available immediately via getTraceId()
  2. The trace ID is passed to the backend (e.g., via an HTTP header) before any network call completes
  3. The backend resumes the trace by passing traceId in TraceOptions

Frontend (Web SDK)

const trace = client.trace({ name: 'TokenSwap' })
  .addAttribute('wallet', userAddress)
  .addEvent('swap:initiated');

// ID is available immediately — no flush needed first
const traceId = trace.getTraceId();

// Pass trace ID to backend via HTTP header
const response = await fetch('/api/swap', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Mirador-Trace-Id': traceId
  },
  body: JSON.stringify(swapParams)
});

Backend (Node.js SDK)

import { Client } from '@miradorlabs/nodejs-sdk';

const mirador = new Client(process.env.MIRADOR_API_KEY);

app.post('/api/swap', async (req, res) => {
  // Resume the frontend trace
  const trace = mirador.trace({
    traceId: req.headers['x-mirador-trace-id'],
    name: 'SwapBackend'
  })
    .addAttribute('endpoint', '/api/swap')
    .addEvent('backend:received');

  try {
    const result = await executeSwap(req.body);
    trace.addEvent('backend:completed', { txHash: result.hash });
    await trace.close('success');
    res.json(result);
  } catch (error) {
    trace.addEvent('backend:error', { message: error.message });
    await trace.close('error');
    res.status(500).json({ error: error.message });
  }
});

Backend → Frontend

You can also create a trace on the backend and resume it on the frontend:
// Backend creates the trace and returns the ID in the response
app.get('/api/session', async (req, res) => {
  const trace = mirador.trace({ name: 'UserSession' })
    .addAttribute('userId', req.user.id)
    .addEvent('session:started');

  // ID is available immediately
  const traceId = trace.getTraceId();

  res.json({ traceId });
});
// Frontend resumes the trace
const { traceId } = await fetch('/api/session').then(r => r.json());

const trace = client.trace({ name: 'UserSession', traceId })
  .addEvent('frontend:loaded');

Best Practices

Name Traces Descriptively

Use clear, action-oriented names:
// Good
client.trace({ name: 'UserSignup' })
client.trace({ name: 'TokenSwap' })
client.trace({ name: 'NFTMint' })

// Avoid
client.trace({ name: 'trace1' })
client.trace({ name: 'data' })

One Trace Per Logical Operation

Create a new trace for each distinct user action or flow:
// User connects wallet
const connectTrace = client.trace({ name: 'WalletConnect' });

// User initiates swap (separate operation)
const swapTrace = client.trace({ name: 'TokenSwap' });

Include Context Early

Add identifying attributes at trace creation:
const trace = client.trace({ name: 'Transfer' })
  .addAttribute('userId', currentUser.id)
  .addAttribute('sessionId', session.id)
  .addAttribute('walletAddress', wallet.address);

Next Steps

Events

Add timestamped events to traces

Attributes & Tags

Enrich traces with metadata