Tracing

Trace context propagation, custom spans, and distributed tracing.

What is Tracing?

Tracing helps you understand the flow of a request or operation through your application. A trace consists of one or more spans, each representing a unit of work. Together, they form a timeline showing exactly what happened and how long each step took.

Bash
# Example trace for a checkout flow:
#
# [Trace: checkout-flow-abc123]
# |
# |-- [Span: validate-cart]          12ms
# |-- [Span: calculate-totals]       3ms
# |-- [Span: process-payment]      245ms
# |   |-- [Span: stripe-api-call]  230ms
# |-- [Span: create-order]          18ms
# |-- [Span: send-confirmation]     45ms
#
# Total: 323ms

Trace Context

Apperio supports trace context via the SDK's context system. You can attach a trace ID and additional context to groups of related log entries:

TypeScript
import Apperio from class="syntax-string">"apperio";

class="syntax-comment">// Generate a unique trace ID
const traceId = crypto.randomUUID();

class="syntax-comment">// Log entries with trace context
Apperio.info(class="syntax-string">"Checkout started", {
  traceId,
  userId: class="syntax-string">"user_123",
  cartItems: class="syntax-number">3,
});

class="syntax-comment">// Later in the flow...
Apperio.info(class="syntax-string">"Payment processed", {
  traceId,             class="syntax-comment">// Same trace ID links these events
  gateway: class="syntax-string">"stripe",
  amount: class="syntax-number">49.99,
});

Apperio.info(class="syntax-string">"Order confirmed", {
  traceId,
  orderId: class="syntax-string">"order_456",
});

Info

All log entries sharing the same traceId can be correlated in the dashboard, giving you a complete picture of the operation.

Custom Spans

Use the context field to create span-like entries that measure the duration of specific operations:

TypeScript
class="syntax-comment">// Measure an async operation
async function processOrder(orderId: string) {
  const traceId = crypto.randomUUID();
  const start = performance.now();

  Apperio.debug(class="syntax-string">"Order processing started", {
    traceId,
    orderId,
    spanName: class="syntax-string">"process-order",
    spanPhase: class="syntax-string">"start",
  });

  try {
    await validateInventory(orderId);
    await chargePayment(orderId);
    await createShipment(orderId);

    const duration = performance.now() - start;
    Apperio.info(class="syntax-string">"Order processing completed", {
      traceId,
      orderId,
      spanName: class="syntax-string">"process-order",
      spanPhase: class="syntax-string">"end",
      duration: Math.round(duration),
    });
  } catch (err) {
    const duration = performance.now() - start;
    Apperio.error(class="syntax-string">"Order processing failed", {
      traceId,
      orderId,
      error: err,
      spanName: class="syntax-string">"process-order",
      spanPhase: class="syntax-string">"error",
      duration: Math.round(duration),
    });
    throw err;
  }
}

class="syntax-comment">// Helper for timing individual steps
async function withSpan(name: string, traceId: string, fn: () => Promise<void>) {
  const start = performance.now();
  try {
    await fn();
    Apperio.debug(name + class="syntax-string">" completed", {
      traceId,
      spanName: name,
      duration: Math.round(performance.now() - start),
    });
  } catch (err) {
    Apperio.error(name + class="syntax-string">" failed", {
      traceId,
      spanName: name,
      duration: Math.round(performance.now() - start),
      error: err,
    });
    throw err;
  }
}

Automatic Tracing

The SDK automatically includes trace-like context with auto-instrumented events:

  • Network requests include request duration and response timing
  • Page loads include navigation timing data
  • Performance entries include measurement timestamps
JSON
// Auto-captured network request with timing:
{
  "level": "info",
  "message": "POST /api/orders 201",
  "eventType": "network",
  "responseTime": 245,
  "data": {
    "method": "POST",
    "url": "/api/orders",
    "status": 201,
    "duration": 245,
    "initiator": "fetch"
  }
}

Distributed Tracing

For applications with a backend that also uses Apperio, you can propagate trace context across the network boundary:

TypeScript
class="syntax-comment">// Frontend: Pass trace ID in request headers
const traceId = crypto.randomUUID();

Apperio.info(class="syntax-string">"Initiating API call", { traceId });

const response = await fetch(class="syntax-string">"/api/process", {
  headers: {
    class="syntax-string">"X-Trace-Id": traceId,
    class="syntax-string">"Content-Type": class="syntax-string">"application/json",
  },
  body: JSON.stringify({ data: class="syntax-string">"..." }),
});

Apperio.info(class="syntax-string">"API call completed", {
  traceId,
  status: response.status,
});
backend/handler.ts
class="syntax-comment">// Backend: Read trace ID from incoming request
app.post(class="syntax-string">"/api/process", (req, res) => {
  const traceId = req.headers[class="syntax-string">"x-trace-id"];

  class="syntax-comment">// Use the same trace ID in backend logs
  logger.info(class="syntax-string">"Processing request", {
    traceId,
    step: class="syntax-string">"validation",
  });

  class="syntax-comment">// Continue propagating to downstream services...
});

Viewing Traces

In the Apperio dashboard, you can filter logs by trace ID to see all events in a single trace. Use the log detail view to examine timing data and identify bottlenecks.

  • Search by traceId in the log stream filter
  • Sort by timestamp to see the chronological flow
  • Compare duration values across spans to find slow operations

Tip

Include the trace ID in error responses sent to users. When they report an issue, you can immediately search for all related events in Apperio using that trace ID.