review-logging-patterns
Review code for logging patterns and suggest evlog adoption. Optionally use @evlog/cli (`evlog init` to wire evlog, `evlog agents` to write the conventions into AGENTS.md, `evlog map` to score entry-point coverage, `--baseline` to gate regressions in CI) on Nuxt, Nitro, Next.js, TanStack Start, and
By evloghq · 748 installs
npx skills add evloghq/evlog --skill review-logging-patterns
Source repository · Upstream listing
Review logging patterns
Review and improve logging patterns in TypeScript/JavaScript codebases. Transform scattered console.logs into structured wide events and convert generic errors into self documenting structured errors.
When to Use
Setting up evlog in a new or existing project (any supported framework)
Reviewing code for logging best practices
Converting console.log statements to structured logging
Improving error handling with better context
Configuring log draining, sampling, or enrichment
Quick Reference
Working on... Resource
Setup (CLI) [ evlog init ](https://www.evlog.dev/cli/init) — wire evlog into the project
Project conventions (CLI) [ evlog agents ](https://www.evlog.dev/cli/agents) — write the evlog block into the project's AGENTS.md
Coverage map (CLI) [ evlog map ](https://www.evlog.dev/cli/map) — score dark entry points
CI gating (CLI) [ evlog map min score / baseline ](https://www.evlog.dev/cli/ci) — gate regressions
Wide events patterns [references/wide events.md](references/wide events.md)
Error handling [references/structured errors.md](references/structured errors.md)
Code review checklist [references/code review.md](references/code review.md)
Drain pipeline [references/drain pipeline.md](references/drain pipeline.md)
Audit logs [build audit logs](../build audit logs/SKILL.md) skill + [docs](https://www.evlog.dev/use cases/audit/overview)
Audit logs
For security sensitive actions (auth, billing, admin, data export), use evlog's audit layer: a typed audit field on wide events, not a parallel logger. See the build audit logs skill for end to end setup ( log.audit , withAudit , denials, auditEnricher , auditOnly , signed , mockAudit ).
Docs: https://www.evlog.dev/use cases/audit/overview
Installation
Use the CLI (recommended on Nuxt, Nitro, Next.js, TanStack Start, Hono)
@evlog/cli is a separate package from evlog , early but worth trying. It reads the project on disk (no traffic, no config). On the five supported frameworks it covers the whole loop: wire evlog in ( init ), score coverage ( map ), lock the score in CI ( min score , baseline ). If the CLI is unavailable, the framework has no adapter yet, or the user declines, continue with the manual sections below; the skill does not depend on it. Ask before installing anything ; prefer npx / pnpm dlx for one shots.
1. Setup: evlog init
On a project that doesn't use evlog yet, prefer init over hand writing the setup, since it detects the framework, reads what the project already has, and generates config, drains, enrichers, and extras in one pass. It is fully scriptable for agents:
Useful flags: framework (override detection: nuxt , nitro , next , tanstack start , hono ), prodDrain (comma separated: axiom , otlp , posthog , sentry , better stack , datadog , hyperdx ), extras ( enrichers , pipeline , sampling , vite , error catalog , audit catalog , ai , better auth ), enrichers , sampling (traffic tier: all , low , medium , high , very high ), apps (monorepo: which workspace packages), no install . Review the dry run output with the user before applying. Docs: https://www.evlog.dev/cli/init
2. Score: evlog map
What you get:
A project score and which entry points are still dark
FIX FIRST : the three most valuable places to fix
GOING FURTHER : opportunities (catalogs, audit coverage, AI logging, auth identity) that never cost points
Per file inspect: npx @evlog/cli map <file no write shows the shape the handler could take
Re run after fixes and watch the score move
Work FIX FIRST in order, keep changes minimal ( useLogger() , log.set() , log.audit() , createError({ why, fix }) ), then re run with no write . Omit no write only when the user wants evlog.map.json written.
3. Lock it in CI: min score and baseline
After fixing, propose making the score durable. This is where the CLI earns its keep:
baseline compares the fresh scan against the committed evlog.map.json , per entry point and per requirement , so a refactor that instruments one route and breaks another fails even if the total score is unchanged. Disabling a passing check with a comment counts as a regression too. New uninstrumented routes are listed as NEW AND DARK without failing. Workflow: commit evlog.map.json once, add the baseline run to CI ( pnpm add D @evlog/cli for a pinned version, and ask first), then re run map without baseline to accept an intentional change. Docs: https://www.evlog.dev/cli/ci
Early days: adapters and rules are still evolving; expect scores to move between releases. Docs: https://www.evlog.dev/cli/map · Rules: https://www.evlog.dev/cli/rules
Framework Setup
Nuxt
useLogger , log , and parseError are auto imported . createError is not: a bare one resolves to h3's, which drops why , fix , and link . Import it from evlog .
Drain, enrich, and tail sampling use Nitro hooks in server plugins:
Client transport (auto configured Vue plugin):
Client side: log , setIdentity , clearIdentity are auto imported in components.
Next.js
Step 1: Create central config. All exports come from here:
Step 2: Wrap route handlers with withEvlog() :
Step 3: Server Actions. Same withEvlog() wrapper:
Step 4: Middleware (optional, sets x request id + timing headers):
Step 5: Client Provider. Wrap the root layout:
Step 6: Client logging. In any client component:
Step 7 (optional): Instrumentation. Startup plus global onRequestError (SSR/RSC errors outside withEvlog ). Use defineNodeInstrumentation(() = import('./lib/evlog')) in root instrumentation.ts to gate Node + cache the import, or write register / onRequestError manually. Both are valid. For custom logic, wrap evlog’s register / onRequestError inside lib/evlog.ts (compose with your own init or metrics), then re export.
Export createInstrumentation() from lib/evlog.ts alongside createEvlog() . See framework docs for coexistence with lockLogger .
Step 8: Client ingest endpoint. Receives client logs:
SvelteKit
Access the logger via event.locals.log in route handlers or useLogger() from anywhere in the call stack:
Full pipeline with drain, enrich, and tail sampling:
Nitro v3
TanStack Start
TanStack Start uses Nitro v3. Install evlog and add a nitro.config.ts :
Add the error handling middleware to root.tsx :
Use useRequest() from nitro/context to access the logger:
Nitro v2
Import useLogger from evlog/nitro in routes.
NestJS
EvlogModule.forRoot() registers a global middleware. Use useLogger() to access the request scoped logger from any controller or service:
Full pipeline with drain, enrich, and tail sampling:
For async configuration with NestJS DI, use forRootAsync() :
Express
Use useLogger() to access the logger from anywhere in the call stack without passing req :
Full pipeline with drain, enrich, and tail sampling:
Hono
Access the logger via c.get('log') in handlers. Use useLogger() from evlog/hono in the layers underneath (services, repositories) where c is not in hand. Both return the same logger:
On Cloudflare Workers, useLogger() needs the nodejs compat (or nodejs als ) compatibility flag; c.get('log') works with or without it.
Structured errors: throw createError() , then in app.onError use parseError() and pass parsed.status as ContentfulStatusCode to c.json() (Hono types the status argument as ContentfulStatusCode , not number ).
Full pipeline with drain, enrich, and tail sampling:
Fastify
request.log is the evlog wide event logger (shadows Fastify's built in pino logger on the request). Fastify's pino logger remains accessible via fastify.log .
Use useLogger() to access the logger from anywhere in the call stack without passing request :
Full pipeline with drain, enrich, and tail sampling:
Elysia
Use useLogger() to access the logger from anywhere in the call stack:
Full pipeline with drain, enrich, and tail sampling:
React Router
Access the logger via context.get(loggerContext) in loaders and actions:
Use useLogger() to access the logger from anywhere in the call stack without passing context:
Full pipeline with drain, enrich, and tail sampling:
oRPC
withEvlog() wraps the handler so each matched request emits one wide event; os.use(evlog()) exposes context.log on every procedure that descends from base and tags the wide event with operation (the procedure path joined with . ).
Use useLogger() to access the logger from utility modules:
Full pipeline with drain, enrich, and tail sampling:
Cloudflare Workers
withEvlog emits one wide event per request when the handler returns, with no manual log.emit() . Async drains are registered with waitUntil so they survive the response; streaming responses defer the emit until the body completes. requestId comes from x request id (fallback cf ray ); method , path , cf ray , traceparent , and the safe subset of request.cf are captured automatically. It accepts the same options ( drain , enrich , keep , include , exclude , routes ) as every other integration. For manual control (scheduled handlers, queues), createWorkersLogger(request) + log.emit() remains available. No ALS based useLogger() on Workers, so pass log explicitly.
AWS Lambda
Lambda has no HTTP middleware lifecycle, so evlog behaves like standalone TypeScript, with one critical rule: one logger per invocation , never a shared module level logger (Lambda reuses execution environments, so a shared instance leaks fields between invocations).
Astro
Type locals.log in src/env.d.ts ( interface Locals { log: RequestLogger } ). Pair with the Vite plugin (below) for auto imports and build time DX.
Vite Plugin (any Vite based framework)
For any Vite based project (SvelteKit, Astro, SolidStart, React+Vite, etc.), use the Vite plugin for auto init, auto imports, and build time features:
Server side middleware (drain, enrich, keep, routes) is still configured in the framework integration (e.g., evlog() middleware for Hono/Express/SvelteKit). The Vite plugin handles build time DX only.
Standalone TypeScript
Configuration Options
All options work in Nuxt ( evlog key), Nitro (passed to evlog() ), Next.js ( createEvlog() ), and standalone ( initLogger() ).
Option Type Default Description
env.service / service string 'app' Service name in logs
enabled boolean true Global toggle (no ops when false)
pretty boolean true in dev Pretty tree format vs JSON
silent boolean false Suppress console output. Events still go to drains
include string[] All routes Route glob patterns to log
exclude string[] None Route patterns to exclude (takes precedence)
routes Record<string, { service } Route specific service names
minLevel 'debug' \ 'info' \ 'warn' \ 'error' 'debug' Hard threshold for the global log API and client log (not request wide events). Use sampling.rates for probabilistic volume on requests
sampling.rates object Head sampling: { info: 10, warn: 50 } (0 100%)
sampling.keep array Tail sampling: [{ status: 400 }, { duration: 1000 }]
drain (ctx) = void Drain callback (Next.js, standalone)
enrich (ctx) = void Enrich callback (Next.js)
keep (ctx) = void Custo