Advanced Usage
Event filtering, child loggers, lifecycle management, serverless, and testing.
beforeSend hook
Filter, modify, or drop events before they're sent. Return the event to send it, or null to drop it:
const logger = init({
beforeSend: (event) => {
// Drop health-check logs
if (event.type === "log" && event.data.message.includes("/health")) {
return null
}
// Scrub PII from error context
if (event.type === "error" && event.data.context?.password) {
delete event.data.context.password
}
return event
},
})The event is a discriminated union with four shapes:
event.type | event.data | Description |
|---|---|---|
"log" | LogEntry | A log entry (debug/info/warn/error) |
"error" | ErrorReport | A captured error |
"trace" | SpanData | A completed span |
"llm" | LLMUsageReport | An LLM usage report |
If the beforeSend callback throws, the event is sent anyway. The SDK never drops events due to a buggy hook.
Child loggers
withContext(name)
Create a logger that tags all events with a context name:
const authLogger = logger.withContext("auth")
authLogger.info("Login successful")
// → context: "auth"forRequest(request)
Create a request-scoped logger that extracts trace IDs from headers:
const reqLogger = logger.forRequest(request)
reqLogger.info("Processing request")
// → trace_id, span_id, request_id from headersBoth child loggers share the same transport and batcher as the parent (one connection, one timer). They get independent copies of user, tags, and contexts.
Lifecycle management
flush()
Send all pending batched logs immediately. Use this before returning a response to ensure logs are delivered:
export async function POST(request: Request) {
logger.info("Webhook received", { type: event.type })
await processWebhook(event)
await logger.flush() // ensure logs are sent
return Response.json({ ok: true })
}destroy(timeout?)
Stop the batch timer, flush remaining logs, and wait for in-flight HTTP requests:
process.on("SIGTERM", async () => {
await logger.destroy(5000) // wait up to 5 seconds
process.exit(0)
})Default timeout is 2000ms. After the timeout, the promise resolves even if requests are still in-flight.
Serverless / Vercel
waitUntil on Vercel
On Vercel, @deeptracer/nextjs automatically wires up waitUntil from @vercel/functions. Logs written after the HTTP response is returned (e.g., from background callbacks) are kept alive until delivery.
No config needed.
Cloudflare Workers
Pass ctx.waitUntil manually:
import { createLogger } from "@deeptracer/core"
export default {
async fetch(request, env, ctx) {
const logger = createLogger({
apiKey: env.DEEPTRACER_KEY,
endpoint: env.DEEPTRACER_ENDPOINT,
waitUntil: ctx.waitUntil.bind(ctx),
})
logger.info("Worker invoked")
return new Response("OK")
// logger keeps the worker alive until the log is sent
},
}Batch timing
On Vercel and AWS Lambda, the default flushIntervalMs is automatically reduced to 200ms (from 5000ms) to ensure logs are flushed before the function freezes.
Using @deeptracer/core directly
For custom runtimes, edge workers, or minimal setups:
import { createLogger } from "@deeptracer/core"
const logger = createLogger({
apiKey: "dt_xxx",
endpoint: "https://your-ingestion.example.com",
service: "edge-worker",
})
logger.info("Running on a custom runtime")@deeptracer/core has zero dependencies and works anywhere with fetch and crypto.getRandomValues().
noopLogger for testing
Use noopLogger in tests to avoid network requests and console noise:
import { noopLogger } from "@deeptracer/core"
// In your test setup or dependency injection
const service = new UserService({ logger: noopLogger })
// All logger methods are safe to call -- they do nothing
noopLogger.info("this goes nowhere")
noopLogger.captureError(new Error("silent"))noopLogger is the same object returned by init() when API keys are missing. It has the full Logger interface: startSpan still calls your callback, flush and destroy resolve immediately, and child logger methods return the same noopLogger.
Next.js route handler and server action wrappers
withRouteHandler
Wrap a Next.js Route Handler with tracing and error capture:
// app/api/users/route.ts
import { withRouteHandler } from "@deeptracer/nextjs"
import { logger } from "@/instrumentation"
export const GET = withRouteHandler(logger, "GET /api/users", async (request) => {
const users = await db.user.findMany()
return Response.json(users)
})withServerAction
Wrap a Server Action with tracing and error capture:
"use server"
import { withServerAction } from "@deeptracer/nextjs"
import { logger } from "@/instrumentation"
export async function createUser(formData: FormData) {
return withServerAction(logger, "createUser", async () => {
const name = formData.get("name") as string
return await db.user.create({ data: { name } })
})
}Both wrappers create a span, capture any thrown errors with "high" severity, re-throw the original error so Next.js error handling still works, and flush before returning.