@jaypie/logger
Prerequisites: npm install jaypie or npm install @jaypie/logger
Overview
Section titled “Overview”@jaypie/logger provides structured JSON logging with request-scoped trace IDs for Datadog integration.
Installation
Section titled “Installation”npm install jaypie# ornpm install @jaypie/loggerQuick Reference
Section titled “Quick Reference”Log Levels
Section titled “Log Levels”| Level | Method | Use Case |
|---|---|---|
| trace | log.trace() |
Happy path checkpoints |
| debug | log.debug() |
Unexpected but handled |
| info | log.info() |
Significant events (rare) |
| warn | log.warn() |
Concerning situations |
| error | log.error() |
Failures |
| fatal | log.fatal() |
Cannot continue |
Additional Methods
Section titled “Additional Methods”| Method | Purpose |
|---|---|
log.var() |
Structured key-value logging |
log.tag() |
Add context to all logs |
log.with() |
Create child logger |
log.init() |
Reset logger state |
log.setup() |
Start a report session |
log.report() |
Accumulate session report data |
log.tally() |
Accumulate combining report data (numbers sum) |
log.teardown() |
Emit session report, end session |
Basic Usage
Section titled “Basic Usage”import { log } from "jaypie";
log.trace("Starting request");log.debug("Cache miss");log.info("Server started");log.warn("Approaching limit");log.error("Service failed");log.fatal("Missing config");Variable Logging
Section titled “Variable Logging”Log structured key-value pairs:
log.var({ userId: "123" });// Output: { log: "trace", message: "123", var: "userId", data: "123" }Available at all levels:
log.trace.var({ requestId: "req-123" });log.debug.var({ cacheHit: false });log.error.var({ errorCode: "E001" });- Single key per call:
// Goodlog.var({ userId: "123" });log.var({ orderId: "456" });
// Badlog.var({ userId: "123", orderId: "456" });- Log summaries, not large objects:
// Goodlog.var({ itemCount: items.length });
// Badlog.var({ items: items });Request Tagging
Section titled “Request Tagging”Add context to all subsequent logs:
log.tag({ requestId: "req-abc-123" });log.tag({ userId: "user-456" });
log.trace("Processing");// 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("Action started");userLog.trace("Action completed");// Both include userIdLogger Initialization
Section titled “Logger Initialization”Reset state between Lambda invocations:
log.init();// Clears tags, resets trace IDHandlers call log.init() automatically.
Session 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; 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.
log.tally({ llm: { operates: 1, turns: 2 } });log.tally({ llm: { operates: 1, turns: 3 } });// => teardown report includes { llm: { operates: 2, turns: 5 } }Outside an active session log.tally() silently no-ops, so libraries can tally unconditionally. @jaypie/llm uses this to report an llm key (turns, tool calls, usage by model) automatically.
Function Prefix Convention
Section titled “Function Prefix Convention”Use bracketed function names:
async function processOrder(orderId) { log.trace("[processOrder] starting"); log.var({ orderId });
await fetchOrder(orderId); log.trace("[processOrder] order fetched");
return order;}Auth Sanitization
Section titled “Auth Sanitization”Authorization headers are automatically redacted before logging to prevent credential leaks:
sk-*tokens →sk_<last 4 chars>- Other values →
md5_<last 4 chars of hash>
Handles top-level authorization keys and nested headers.authorization. Original objects are never mutated.
Configuration
Section titled “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 startSerialization Limits
Section titled “Serialization Limits”Entries are capped at 256KB by default — the CloudWatch Logs event limit that fronts Datadog in Lambda. Oversized entries truncate the top-level attributes of data largest-first, each keeping a 72-character preview plus a visible marker:
data:application/pdf;base64,JVBERi0xLjcK… [truncated 612,340 chars]Two more limits are available, off by default: maxStringLength (truncate each string field beyond N characters) and maxDepth (replace objects nested beyond N levels with [Object] / [Array(n)]).
Override at runtime with log.config() — it propagates to derived loggers and persists across init():
import { log } from "jaypie";
log.config({ maxStringLength: 1024 });log.config({ maxEntryBytes: false }); // false disables a limitLimits apply at serialization time only; the logged object is never mutated.
JSON Output
Section titled “JSON Output”log.info("Server started");Outputs:
{ "status": "info", "message": "Server started", "invoke": "abc-123", "timestamp": "2024-01-15T10:30:00.000Z"}Level Guidelines
Section titled “Level Guidelines”Normal execution flow:
async function getUser(id) { log.trace("[getUser] fetching"); const user = await db.findById(id); log.trace("[getUser] found"); return user;}Unexpected but handled:
const cached = cache.get(id);if (!cached) { log.debug("[getUser] cache miss"); return await fetchUser(id);}Significant events (rare):
log.info("Server started");log.info("Migration complete");Concerning situations:
if (usage > limit * 0.8) { log.warn("Approaching rate limit"); log.var({ usage });}Failures:
try { await externalApi.call();} catch (error) { log.error("External API failed"); log.var({ error: error.message }); throw BadGatewayError();}Cannot continue:
if (!process.env.MONGODB_URI) { log.fatal("Missing MONGODB_URI"); process.exit(1);}Datadog Log Forwarding
Section titled “Datadog Log Forwarding”For local dev, eval runs, and CI where CloudWatch subscription filters aren’t available, enable direct HTTP forwarding to Datadog’s Logs API:
DATADOG_LOCAL_FORWARDING=true DATADOG_API_KEY=your-key npm run devZero code changes — the logger auto-detects the env vars and ships logs alongside normal console output.
| Variable | Description | Default |
|---|---|---|
DATADOG_LOCAL_FORWARDING |
Enable forwarding | false |
DATADOG_API_KEY |
Datadog API key (required) | — |
DD_SITE |
Datadog intake site | datadoghq.com |
DD_SERVICE / PROJECT_SERVICE |
Service tag | unknown |
DD_ENV / PROJECT_ENV |
Environment tag | local |
DD_HOST / PROJECT_HOST |
Hostname tag | os.hostname() |
import { isDatadogForwardingEnabled } from "@jaypie/logger";
if (isDatadogForwardingEnabled()) { // Transport is active}Testing
Section titled “Testing”import { log } from "jaypie";import { spyLog, restoreLog } from "@jaypie/testkit";
beforeEach(() => { spyLog(log);});
afterEach(() => { restoreLog(log);});
it("logs at trace only", async () => { await successfulOperation(); expect(log.trace).toHaveBeenCalled(); expect(log.debug).not.toHaveBeenCalled();});Related
Section titled “Related”- Logging - Best practices
- Testing - Testing logs
- Handler Lifecycle - Automatic initialization