Distributed Tracing Example
A complete example showing how to trace a multi-step operation including database queries, external API calls, and correlated logs.
Setup
import { LightningLogsClient } from '@lightning-logs/sdk'
const client = new LightningLogsClient({
baseURL: process.env.SUPABASE_URL!,
getAuthToken: async () => process.env.LIGHTNING_LOGS_API_KEY!,
serviceName: 'api',
environment: process.env.NODE_ENV ?? 'production',
})Tracing an HTTP Request Handler
Trace a Next.js API route end-to-end with child spans for each operation
// app/api/orders/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { client } from '@/lib/lightning-logs'
export async function POST(request: NextRequest) {
const body = await request.json()
const trace = client.startTrace({
name: 'api.create-order',
metadata: {
route: '/api/orders',
method: 'POST',
},
})
try {
// Validate input
const validateSpan = trace.startSpan('validate-input')
const { userId, items } = validateOrder(body)
validateSpan.end({ status: 'ok', metadata: { item_count: items.length } })
// Check inventory
const inventorySpan = trace.startSpan('db.check-inventory')
const available = await checkInventory(items)
inventorySpan.end({ status: 'ok', metadata: { all_available: available } })
if (!available) {
trace.log.warn('Order failed: items out of stock', {
user_id: userId,
attrs: { item_count: items.length },
})
await trace.end({ status: 'error' })
return NextResponse.json({ error: 'Out of stock' }, { status: 409 })
}
// Create order in DB
const dbSpan = trace.startSpan('db.create-order')
const order = await db.transaction(async (trx) => {
const insertOrderSpan = dbSpan.startSpan('insert-order')
const o = await trx.insert(orders).values({ userId }).returning()
insertOrderSpan.end({ status: 'ok' })
const insertItemsSpan = dbSpan.startSpan('insert-order-items')
await trx.insert(orderItems).values(items.map(i => ({ orderId: o.id, ...i })))
insertItemsSpan.end({ status: 'ok', metadata: { count: items.length } })
return o
})
dbSpan.end({ status: 'ok', metadata: { order_id: order.id } })
// Charge payment
const paymentSpan = trace.startSpan('stripe.charge')
const charge = await stripe.paymentIntents.create({
amount: calculateTotal(items),
currency: 'usd',
customer: userId,
})
paymentSpan.end({ status: 'ok', metadata: { charge_id: charge.id } })
trace.log.info('Order created', {
user_id: userId,
attrs: {
order_id: order.id,
item_count: items.length,
total_cents: calculateTotal(items),
},
})
await trace.end({ status: 'ok' })
return NextResponse.json({ orderId: order.id })
} catch (error: any) {
trace.log.error('Order creation failed', {
attrs: { error: error.message },
})
await trace.end({ status: 'error' })
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}Using trace.wrap() for Cleaner Code
The wrap() helper removes boilerplate — spans and trace end automatically
await client.tracer.trace(
{ name: 'process-batch', metadata: { batch_size: jobs.length } },
async (trace) => {
for (const job of jobs) {
await trace.wrap(`process-job.${job.type}`, async (span) => {
const fetchSpan = span.startSpan('fetch-data')
const data = await fetchJobData(job.id)
fetchSpan.end({ status: 'ok' })
const processSpan = span.startSpan('transform')
const result = await transform(data)
processSpan.end({ status: 'ok', metadata: { output_size: result.length } })
trace.log.info(`Job ${job.id} complete`, {
attrs: { job_type: job.type, duration_hint: 'see span' }
})
})
}
}
)Tracking AI Calls
Mark spans as AI calls to see model usage and cost in the traces view
const trace = client.startTrace({ name: 'ai.summarize-document' })
const extractSpan = trace.startSpan('extract-text')
const text = await extractPdfText(documentUrl)
extractSpan.end({ status: 'ok', metadata: { char_count: text.length } })
const llmSpan = trace.startSpan('openai.summarize', {
isAI: true,
model: 'gpt-4o-mini',
})
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: 'Summarize the following document.' },
{ role: 'user', content: text },
],
})
llmSpan.end({
status: 'ok',
tokens: response.usage?.total_tokens,
cost: (response.usage?.total_tokens ?? 0) * 0.00000015, // $0.15 / 1M tokens
metadata: { finish_reason: response.choices[0].finish_reason },
})
trace.log.info('Document summarized', {
attrs: {
document_url: documentUrl,
tokens_used: response.usage?.total_tokens,
},
})
await trace.end({ status: 'ok' })Frontend Page Tracing
Trace page navigations in Next.js — automatically correlates page view logs to the trace
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
import { client } from '@/lib/lightning-logs'
export function TraceInit() {
const pathname = usePathname()
useEffect(() => {
const trace = client.startTrace({
name: 'ui.page_navigation',
metadata: { path: pathname },
})
const renderSpan = trace.startSpan('ui.route_render', {
metadata: { path: pathname },
})
// This log is automatically correlated to the trace ID
trace.log.info(`Page view: ${pathname}`, {
type: 'analytics',
event_type: 'page_view',
route: pathname,
})
renderSpan.end({ status: 'ok' })
trace.end({ status: 'ok' }).catch(() => {})
}, [pathname])
return null
}