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