Logging
Prerequisites: npm install jaypie (includes @jaypie/logger)
Overview
Section titled “Overview”Jaypie provides structured logging with request-scoped trace IDs. Logs are JSON-formatted for Datadog ingestion. Follow the log level conventions to maintain clean, actionable logs.
Quick Reference
Section titled “Quick Reference”| Level | Use Case | Example |
|---|---|---|
trace |
Happy path checkpoints | Entering function, completing step |
debug |
Unexpected but handled cases | Cache miss, retry attempt |
info |
Significant events (rare) | Server started, migration complete |
warn |
Concerning but recoverable | Deprecation, approaching limit |
error |
Failures requiring attention | External service down |
fatal |
Application cannot continue | Missing critical config |
Basic Usage
Section titled “Basic Usage”import { log } from "jaypie";
log.trace("Starting request processing");log.debug("Cache miss, fetching from database");log.info("Server started on port 3000");log.warn("API rate limit at 80%");log.error("External payment service unavailable");log.fatal("Missing required MONGODB_URI");Variable Logging
Section titled “Variable Logging”Use log.var() for structured key-value logging:
// Single key-value pair (preferred)log.var({ userId: "abc-123" });
// Output:// { "status": "trace", "message": "abc-123", "var": "userId", "data": "abc-123", "dataType": "string" }Variable logging is available at all levels:
log.trace.var({ requestId: "req-123" });log.debug.var({ cacheHit: false });log.error.var({ errorCode: "E001" });Variable Logging Rules
Section titled “Variable Logging Rules”- Single key per call - Log one variable at a time:
// Goodlog.var({ userId: "123" });log.var({ orderId: "456" });
// Bad - multiple keyslog.var({ userId: "123", orderId: "456" });- Don’t log large structures - Log summaries instead:
// Goodlog.var({ userCount: users.length });
// Bad - large arraylog.var({ users: users });- Separate strings from variables:
// Goodlog.trace("[processOrder] starting");log.var({ orderId: order.id });
// Bad - mixing string and variablelog.trace(`Processing order ${order.id}`);Log Level Guidelines
Section titled “Log Level Guidelines”Trace (Happy Path)
Section titled “Trace (Happy Path)”Use for normal execution flow. Should be the only level in successful requests:
async function getUser(id) { log.trace("[getUser] fetching user"); const user = await db.users.findById(id); log.trace("[getUser] user found"); return user;}Debug (Off Happy Path)
Section titled “Debug (Off Happy Path)”Use when something unexpected happens but is handled:
async function getCachedUser(id) { const cached = cache.get(id); if (!cached) { log.debug("[getCachedUser] cache miss"); return await fetchAndCacheUser(id); } return cached;}Info (Rarely Used)
Section titled “Info (Rarely Used)”Reserve for significant application events:
// Server lifecyclelog.info("Server started");
// Major state changeslog.info("Database migration complete");Warn (Concerning Situations)
Section titled “Warn (Concerning Situations)”Use for recoverable issues that need attention:
if (apiCallsThisHour > limit * 0.8) { log.warn("Approaching API rate limit"); log.var({ usage: apiCallsThisHour });}Error (Failures)
Section titled “Error (Failures)”Use for failures that need investigation:
try { await externalService.call();} catch (error) { log.error("External service call failed"); log.var({ service: "payment" }); throw BadGatewayError();}Fatal (Application Cannot Continue)
Section titled “Fatal (Application Cannot Continue)”Use when the application must stop:
if (!process.env.MONGODB_URI) { log.fatal("Missing required MONGODB_URI"); process.exit(1);}Request Tagging
Section titled “Request Tagging”Tag all subsequent logs with context:
import { log } from "jaypie";
// Tag logs for this requestlog.tag({ requestId: "req-abc-123" });log.tag({ userId: "user-456" });
// All subsequent logs include these tagslog.trace("Processing request");// Output includes: { requestId: "req-abc-123", userId: "user-456", ... }Child Loggers
Section titled “Child Loggers”Create loggers with additional context:
const userLog = log.with({ userId: user.id });
userLog.trace("User action started");userLog.trace("User action completed");// Both logs include userIdLambda Initialization
Section titled “Lambda Initialization”Reset logger state between Lambda invocations:
import { lambdaHandler, log } from "jaypie";
export const handler = lambdaHandler(async (event) => { // log.init() called automatically by handler log.trace("Processing event");});For manual initialization:
log.init(); // Clears tags, resets stateSession Management
Section titled “Session Management”Handlers automatically call log.setup() and log.teardown() to bookend each request. On teardown, a report is emitted as log.info.var({ report }) containing accumulated data and warn/error counts.
// Manual session (handlers do this automatically)log.setup({ handler: "myHandler", invoke: "abc-123" });
// Accumulate report data during the requestlog.report({ userId: "456" });log.report({ itemCount: 3 });
// Teardown emits the report with warn/error statslog.teardown();// => { report: { userId: "456", itemCount: 3, log: { warn: false, warns: 0, error: false, errors: 0 } } }log.report() only accumulates data while a session is active (after setup(), before teardown()); calling it outside a session logs a warning and is a no-op. Warn and error calls made during the session are counted automatically and included in the final report.
log.report() records values once. Plain objects merge recursively, so separate calls can fill sibling keys under one parent (report({ agent: { a: {...} } }) then report({ agent: { b: {...} } }) keeps both). Arrays and scalars are leaves: the last write wins, and a changed leaf logs [logger] Overwriting report key: <dotted.path> at debug. Use log.tally() for data that accumulates — keys combine instead: numbers sum, strings collect into an array of strings, booleans AND, and objects merge recursively. Outside an active session log.tally() silently no-ops, so libraries can tally unconditionally.
log.tally({ llm: { operates: 1, turns: 2 } });log.tally({ llm: { operates: 1, turns: 3 } });// => teardown report includes { llm: { operates: 2, turns: 5 } }log.sessionActive reports whether a session is open. Libraries guard optional tally work with log.sessionActive !== false, which keeps the guard correct against an older logger that predates the accessor.
Inside a handler, @jaypie/llm tallies an llm key automatically — operate() and stream() report turns, tool calls, and usage by model with no code changes.
Environment Configuration
Section titled “Environment Configuration”| Variable | Values | Default |
|---|---|---|
LOG_LEVEL |
trace, debug, info, warn, error, fatal | info |
LOG_FORMAT |
json, text | json |
LOG_MAX_ENTRY_BYTES |
bytes; false disables |
262144 (256KB) |
LOG_MAX_STRING |
characters; false disables |
off |
LOG_MAX_DEPTH |
levels; false disables |
off |
LOG_LEVEL=debug npm startOversized entries truncate deliberately with visible markers (… [truncated 612,340 chars]) so payloads fit the log pipeline — see Serialization Limits for details and log.config() runtime overrides.
Function Prefix Convention
Section titled “Function Prefix Convention”Use bracketed function names in log messages:
async function processOrder(orderId) { log.trace("[processOrder] starting"); log.var({ orderId });
const order = await fetchOrder(orderId); log.trace("[processOrder] order fetched");
await validateOrder(order); log.trace("[processOrder] order validated");
return order;}Testing Logs
Section titled “Testing Logs”Use @jaypie/testkit for log assertions:
import { log } from "jaypie";import { spyLog, restoreLog } from "@jaypie/testkit";
beforeEach(() => { spyLog(log);});
afterEach(() => { restoreLog(log);});
it("logs at trace level on happy path", async () => { await processOrder("order-123");
// Verify no logs above trace expect(log.debug).not.toHaveBeenCalled(); expect(log.warn).not.toHaveBeenCalled(); expect(log.error).not.toHaveBeenCalled();});
it("logs error on failure", async () => { await expect(processOrder("invalid")).rejects.toThrow(); expect(log.error).toHaveBeenCalled();});Related
Section titled “Related”- Error Handling - Error types and when to log errors
- Handler Lifecycle - Automatic log initialization
- @jaypie/logger - Full API reference
- Testing - Testing log output