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, Express, Fastify) The evlog executable ships with the evlog package: pnpm evlog , npx evlog , bunx evlog . It runs @evlog/cli when installed and fetches it on demand otherwise (no dependency added to the project), so it works on a project with nothing yet. Early but worth trying. It reads the project on disk (no traffic, no config needed). On the seven 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 . 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 , express , fastify ), prodDrain (comma separated: axiom , otlp , posthog , sentry , better stack , datadog , hyperdx , sazabi , loki , clickhouse ), 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 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 On GitHub Actions, suggest the [evlog action](https://github.com/evloghq/action) ( uses: evloghq/action@v1 with min score ): it scans the pull request base itself, so no map file has to be committed, and puts regressions on the diff and in one comment. The commands below are the same gate from a run: step. 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, then re run map without baseline to accept an intentional change. Docs: https://www.evlog.dev/cli/ci When the project has an evlog.config.ts , the gate can live there instead of in the CI step: map: { minScore: 80, baseline: true } makes a bare evlog map gate the same way, and a flag still wins. The same map block turns checks off for every entry point ( rules: { 'error catalog': 'off' } ) and leaves entry points out by file glob ( ignore ). The CLI reads these values without running the file, so they must be literals. Docs: https://www.evlog.dev/cli/config 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, sampling and redaction can live in evlog.config.ts at the app root. The module loads it on the server, and the evlog key in nuxt.config.ts overrides it. The browser logger reads only the evlog key. Nitro hooks in server plugins keep working next to the file, and an event reaches both drains: 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. On Nitro v2 and v3, an evlog.config.ts at the app root is loaded by the module, and the options passed to evlog() override it. NestJS EvlogModule.forRoot() registers a global middleware, on both @nestjs/platform express and @nestjs/platform fastify . req.log in controllers is the evlog logger on both platforms. Do not also register evlog/fastify on a Nest Fastify app, or every request emits two wide events. 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: Effect Effect 4 ( HttpRouter , HttpApi ). routerLayer installs the evlog() middleware on every route and provides the EvlogLogger service. initLoggerLayer calls initLogger when the layer builds and flushes a createDrainPipeline drain when its scope closes: There is no AsyncLocalStorage and no log.fork() . The event lives in the Effect context: yield EvlogLogger in handlers and services (the requirement shows up in the effect's type), yield useLogger<Fields () for typed fields, and yield set(fields) in library code that should not require EvlogLogger (a no op outside a unit). It runs on Cloudflare Workers without nodejs compat . Failures are read from the Cause (the primary failure as error , the rest under error.suppressed , a pure interruption as status: 499 ). Effect's router answers a failed createError({ status: 402 }) with 500, so catch it in the handler with Effect.catch , call log.error(error) , and return HttpServerResponse.jsonUnsafe(body, { status: parsed.status }) built from parseError(error) . HttpApi encodes declared errors as responses before the middleware: the event gets the status but no error field. Non HTTP work (jobs, queue consumers, Durable Object alarms) uses .pipe(withLogger('action name', options)) . A Durable Object fetch uses .pipe(wi