Builder
Accumulate context and create wide events
The Builder class accumulates context during a request and produces a wide event output.
Creating a Builder
const builder = afterlog.createBuilder({
http_method: "GET",
path: "/api/users",
})Or use the Builder class directly:
import { Builder } from "afterlog"
const builder = new Builder<T>(config, init?)Options
Config (first argument):
service- Service nameversion- Service versionregion- Deployment regiondeployment_id- Git SHA or build numberenvironment- production/staging/etcclock- Custom timing implementationtimeout_ms- Timeout threshold
Init (second argument):
request_id- Override request UUIDtrace_id- Override trace UUID (for distributed tracing)method- HTTP methodpath- Request pathheaders- Headers for trace extraction
Setting Fields
set(key, value)
Sets a top-level field. Overwrites existing values.
builder.set("user_id", "123")
builder.set("http_status_code", 200)merge(key, value)
Deep merges into a nested object. Creates the field if it doesn't exist.
builder.merge("user", { id: "123" })
builder.merge("user", { tier: "premium" })
// Result: { id: "123", tier: "premium" }enrich(key, value)
Adds arbitrary metadata field. Runs at finalize time.
builder.enrich("expensive_computation", compute())Warns if the key contains sensitive words like "password", "token", "secret".
Timing Operations
timing(name, fn)
Times an async function automatically:
const result = await builder.timing("database", () => db.getUser(id))Returns the function's result. Records timing even if the function throws.
time(name)
Starts a manual timer:
builder.time("external_api")timeEnd(name)
Ends a timer and returns duration:
const duration = builder.timeEnd("external_api")
// duration: 120Throws if no matching time() call.
Error Handling
error(err, context?)
Captures an error with optional context:
builder.error(new Error("failed"))
builder.error(err, { component: "payment", amount: 100 })Error is normalized with:
type- Error constructor namemessage- Error messagestack- Stack trace (if available)context- Your provided context
hasErrors()
Returns true if any errors captured:
if (builder.hasErrors()) {
builder.set("retry_count", 1)
}Finalizing
finalize()
Completes the event and returns immutable output:
const event = builder.finalize()
// Returns readonly object with:
// - request_id
// - trace_id
// - timestamp
// - duration_ms
// - timings
// - error (if any)
// - outcome: "success" | "error" | "timeout"Usually you'll call afterlog.finalize(builder) instead, which handles sampling.
Reading State
snapshot_()
Returns current state without finalizing:
const snapshot = builder.snapshot_()Uses caching - returns same reference until modified.
state_
Current lifecycle state: created | building | finalizing | emitted
console.log(builder.state_) // "building"id
Unique builder instance UUID (readonly).
trace_id
Trace UUID (readonly). Auto-generated or from init.
Example: Express Middleware
function loggingMiddleware() {
return (req, res, next) => {
const builder = afterlog.createBuilder({
http_method: req.method,
path: req.path,
})
req.log = builder
const start = Date.now()
res.on("finish", () => {
builder.set("http_status_code", res.statusCode)
builder.set("duration_ms", Date.now() - start)
afterlog.finalize(builder)
})
next()
}
}
app.use(loggingMiddleware())
app.get("/users/:id", async (req, res) => {
const user = await req.log.timing("db", () => db.getUser(req.params.id))
req.log.set("user_id", user.id)
res.json(user)
})