Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
review-logging-patterns
description
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 Hono. Guides setup on those plus SvelteKit, React Router, NestJS, Express, Fastify, Elysia, oRPC, Cloudflare Workers, AWS Lambda, Astro, and standalone TypeScript. Detects console.log spam, unstructured errors, and missing context. Covers wide events, structured errors, drain adapters (Axiom, OTLP, HyperDX, PostHog, Sentry, Better Stack, Datadog, Loki, ClickHouse, NuxtHub, Memory), sampling, enrichers, and AI SDK integration.
license
MIT
metadata
{"author":"HugoRCD","version":"0.9"}
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
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).
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:
# preview everything without writing (always start here)
npx @evlog/cli init --dry-run --yes
# then apply — flags instead of prompts
npx @evlog/cli init --yes \
--service my-app \
--drain fs \
--prodDrain axiom \
--extras enrichers,pipeline,sampling \
--sampling medium
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
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:
# in CI, after pnpm add -D @evlog/cli (project-local, pinned by the lockfile)
pnpm exec evlog map --min-score 80 # absolute gate: exits 1 below the threshold
pnpm exec evlog map --baseline # ratchet: exits 1 if this PR made things worse
--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
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.
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.
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:
import { useLogger } from 'evlog/hono'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
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).
import { createError, parseError } from 'evlog'
import type { ContentfulStatusCode } from 'hono/utils/http-status'
app.onError((error, c) => {
c.get('log').error(error)
const parsed = parseError(error)
return c.json(
{ message: parsed.message, why: parsed.why, fix: parsed.fix, link: parsed.link },
parsed.status as ContentfulStatusCode,
)
})
Full pipeline with drain, enrich, and tail sampling:
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:
import { useLogger } from 'evlog/fastify'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
Full pipeline with drain, enrich, and tail sampling:
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:
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).
import { initLogger, createLogger } from 'evlog'
initLogger({ env: { service: 'my-fn' } }) // once at module load (cold start)
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
const log = createLogger({ messageId: record.messageId })
try {
log.set({ queue: { source: record.eventSourceARN } })
await processMessage(record)
} catch (error) {
log.error(error as Error)
throw error
} finally {
log.emit()
}
}
}
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.
None (in-process ring buffer; optional EVLOG_MEMORY_STORE, EVLOG_MEMORY_MAX_EVENTS). Read back with readMemoryLogs() — ideal for dev-only log endpoints agents can query
NuxtHub
@evlog/nuxthub (separate package, Nuxt module)
None — stores wide events in the NuxtHub database with retention-based cleanup (set evlog.retention: '7d' in the module options; accepts d/h/m)
HTTP (browser ingest)
evlog/http
None (configure endpoint in code). evlog/browser is deprecated; same API, removed next major
Use canonical env var names (e.g. AXIOM_API_KEY, BETTER_STACK_API_KEY), and the same names work in every framework.
Built-in: createUserAgentEnricher(), createGeoEnricher(), createRequestSizeEnricher(), createTraceContextEnricher(), all from evlog/enrichers. Each accepts { overwrite?: boolean } (default false). Use createDefaultEnrichers() to compose all four in one call:
import { createDefaultEnrichers } from 'evlog/enrichers'
app.use(evlog({ enrich: createDefaultEnrichers() }))
// Nuxt/Nitro: server/plugins/evlog-enrich.ts
import { createUserAgentEnricher, createGeoEnricher } from 'evlog/enrichers'
export default defineNitroPlugin((nitroApp) => {
const enrichers = [createUserAgentEnricher(), createGeoEnricher()]
nitroApp.hooks.hook('evlog:enrich', (ctx) => {
for (const enricher of enrichers) enricher(ctx)
})
})
// Next.js: in lib/evlog.ts
createEvlog({
enrich: (ctx) => {
for (const enricher of enrichers) enricher(ctx)
ctx.event.region = process.env.VERCEL_REGION
},
})
Auto-Redaction (PII Protection)
Built-in redaction scrubs sensitive data from wide events before console output and before any drain sees the data. Enabled by default in production (NODE_ENV === 'production'), disabled in development. Uses smart partial masking, preserving enough context for debugging.
// Disable in production (opt-out)
evlog: { redact: false }
// Add custom paths on top of built-ins
evlog: {
redact: {
paths: ['user.password', 'headers.authorization'],
}
}
// Only specific built-ins
evlog: {
redact: {
builtins: ['email', 'creditCard'],
}
}
// No built-ins, only custom (uses flat [REDACTED] replacement)
evlog: {
redact: {
builtins: false,
paths: ['user.ssn'],
patterns: [/SECRET_\w+/g],
}
}
Built-in patterns with smart masking output:
Pattern
Example Input
Masked Output
creditCard
4111111111111111
****1111
email
alice@example.com
a***@***.com
ipv4
192.168.1.100
***.***.***.100
phone
+33 6 12 34 56 78
+33 ****5678
jwt
eyJhbGciOi...
eyJ***.***
bearer
Bearer sk_live_abc...
Bearer ***
iban
FR76 3000 6000 ...189
FR76****189
Works in all frameworks: Nuxt (evlog config), Nitro (evlog() module options), Next.js (createEvlog()), standalone (initLogger()), and all middleware integrations (Hono, Express, Fastify, Elysia, NestJS).
AI SDK Integration
Capture token usage, tool calls, model info, streaming metrics, tool execution timing, cost estimation, and embedding metadata from the Vercel AI SDK into wide events. Import from evlog/ai. Requires ai >=6.0.168 <8.0.0 as a peer dependency.
Basic setup (middleware)
import { createAILogger } from 'evlog/ai'
const log = useLogger(event) // or any RequestLogger
const ai = createAILogger(log)
const result = streamText({
model: ai.wrap('anthropic/claude-sonnet-4.6'), // accepts string or model object
messages,
})
ai.wrap() uses model middleware to transparently capture all LLM calls. Works with generateText, streamText, and ToolLoopAgent.
Telemetry integration (deeper observability)
For tool execution timing, success/failure tracking, and total generation wall time, add createEvlogIntegration():