Distributed Tracing
What’s a trace ID?
Section titled “What’s a trace ID?”A trace ID is a string that links related log entries together. You generate one at the start of an operation and pass it to every log in that operation. When you log with the same traceId, you can see the full journey of a request — from frontend button click, through API route, to database query and back.
const traceId = `checkout-${orderId}-${Date.now()}`;
gunsole.info({ bucket: "api", message: "Checkout started", traceId, tags: { step: "start" },});
// ... latergunsole.info({ bucket: "payment", message: "Payment processed", traceId, tags: { step: "payment" },});
// ... latergunsole.info({ bucket: "api", message: "Checkout completed", traceId, tags: { step: "complete" },});In the desktop app, you can search or filter by trace ID to see the entire flow of an operation across buckets.
Generating trace IDs
Section titled “Generating trace IDs”Use whatever format makes sense for your app. Some patterns:
// UUID-basedconst traceId = crypto.randomUUID();
// Descriptive prefix (from example apps)const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
// Semantic (e.g., for checkout flows)const traceId = `checkout:order-${orderId}`;The only requirement is that it’s a string. Make it descriptive enough that you can identify the operation when you see it in the log viewer.
Cross-service tracing
Section titled “Cross-service tracing”Frontend → generates traceId, passes it to backend via header
Section titled “Frontend → generates traceId, passes it to backend via header”const traceId = crypto.randomUUID();
gunsole.info({ bucket: "checkout", message: "Submitting order", traceId, context: { items: cart.length },});
const response = await fetch("/api/orders", { method: "POST", headers: { "x-trace-id": traceId }, body: JSON.stringify({ items: cart }),});Backend → reads traceId from header, uses it in all related logs
Section titled “Backend → reads traceId from header, uses it in all related logs”app.post("/api/orders", async (req, res) => { const traceId = req.headers["x-trace-id"] as string;
gunsole.info({ bucket: "checkout", message: "Processing order", traceId, context: { userId: req.user.id }, });
try { const order = await db.createOrder(req.body);
gunsole.info({ bucket: "checkout", message: "Order created", traceId, context: { orderId: order.id }, });
res.json(order); } catch (err) { gunsole.error({ bucket: "checkout", message: "Order failed", traceId, context: { error: err.message }, });
res.status(500).json({ error: "Order failed" }); }});Both logs — frontend and backend — share the same trace ID. Filter by it in the desktop app to see the full request lifecycle.
Tracing pattern from example apps
Section titled “Tracing pattern from example apps”All example apps (React, Solid, Angular) use this pattern for API calls:
const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;const startTime = performance.now();
// Log at startgunsole.info({ bucket: "api_request", message: `Fetching: ${url}`, tags: { api: "pokeapi", action: "fetch_start" }, traceId,});
try { const response = await fetch(url); const totalTime = performance.now() - startTime;
// Log on success with timing gunsole.info({ bucket: "api_request", message: `Fetched successfully`, context: { totalTimeMs: Math.round(totalTime) }, tags: { api: "pokeapi", action: "fetch_success", status: "200" }, traceId, });} catch (err) { const totalTime = performance.now() - startTime;
// Log on failure with timing gunsole.error({ bucket: "api_request", message: `Fetch failed`, context: { error: err.message, totalTimeMs: Math.round(totalTime) }, tags: { api: "pokeapi", action: "fetch_error" }, traceId, });}