logging.md 4.0 KB

Logging

Overview

All server-side logging goes through Winston. A single root logger is created at module load and shared across the process. Each domain gets a child logger that automatically attaches a domain field to every log entry, making it easy to filter, tail, and route logs by domain in any log aggregation system.

Logging is server-only. Client-side code does not import from $lib/server/.

Request Tracing

Every request is assigned a UUID (requestId) generated in handle() and stored in both event.locals.requestId and the request-scoped AsyncLocalStorage context (src/lib/server/context.ts).

A custom Winston format reads from AsyncLocalStorage on every log call and injects requestId automatically. This means every log line emitted anywhere during a request — across domain loggers, infrastructure clients, auth — carries the same requestId with no manual threading required.

Log Format

Format is selected by NODE_ENV:

NODE_ENV Format Use case
development Colorized, human-readable HH:mm:ss LEVEL [domain] (requestId) message {...extra} Local dev
anything else Structured JSON with timestamp, level, domain, requestId, message, and any extra fields Production / staging

JSON output is structured so every field is at the top level — no nested meta objects — which makes querying in tools like Loki, Elasticsearch, or CloudWatch straightforward.

Log Levels

Standard Winston levels in descending severity: error, warn, info, http, verbose, debug, silly.

Set LOG_LEVEL env var to control the minimum level emitted (default: info).

Domain Loggers

Each server module imports its own named logger from $lib/server/logger.ts. The domain field is injected automatically by the child logger — callers never set it manually.

Logger export Domain tag Used in
logger (root, no tag) General / unclassified
serverLogger server server.ts, hooks.server.ts
authLogger auth auth.ts
gitLogger git git/github.ts, git/cache.ts
k8sLogger k8s k8s.ts

Domain-specific loggers for agents, models, tools, knowledge-bases, and gateways are added as those modules are implemented.

Usage

import { serverLogger as log } from '$lib/server/logger';

log.info('Server ready');
log.warn('K8s CR not found', { name, namespace });
log.error('Failed to open PR', { err, agentName }); // always pass error as `err` for stack capture

Pass structured fields as the second argument rather than interpolating into the message string. This keeps the message string stable (searchable as a constant) and fields queryable individually.

What to Log

Event Level Fields
Server init / shutdown info
Component initialized info component name, key config (no secrets)
Login success info username
Login failure warn username (never log passwords)
Outbound API call debug url, method
Outbound API error error url, status, err
Unhandled exception error err (stack)

Never log secrets, passwords, tokens, or full request/response bodies.