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() warns when a key is written twice. Use log.tally() for data written repeatedly — 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 } }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