Loading...
Loading...
Review code for logging patterns and suggest evlog adoption. Detects console.log spam, unstructured errors, and missing context. Guides wide event design, structured error handling, request-scoped logging, and log draining with adapters (Axiom, OTLP).
npx skill4agent add hugorcd/evlog review-logging-patternsconsole.loguseLogger(event)throw new Error('...')createError({ message, status, why, fix })useLogger(event)createRequestLogger()| Working on... | Resource |
|---|---|
| Wide events patterns | references/wide-events.md |
| Error handling | references/structured-errors.md |
| Code review checklist | references/code-review.md |
| Log draining & adapters | See "Log Draining & Adapters" section below |
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
// useLogger is auto-imported - no import needed!
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
return { success: true }
})<!-- In Vue components - log is auto-imported -->
<script setup>
log.info('checkout', 'User initiated checkout')
</script>// ❌ Scattered logs - impossible to correlate during incidents
console.log('Request received')
console.log('User authenticated')
console.log('Loading cart')
console.log('Processing payment')
console.log('Payment failed')// server/api/checkout.post.ts
// No import needed in Nuxt - useLogger is auto-imported!
// ✅ One comprehensive event per request
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: '123', plan: 'premium' } })
log.set({ cart: { items: 3, total: 9999 } })
log.error(error, { step: 'payment' })
// emit() called automatically at request end
})// ❌ Multiple logs for one logical operation
console.log('Starting checkout')
console.log('User:', userId)
console.log('Cart:', cart)
console.log('Payment result:', result)// ✅ Single wide event
log.info({
action: 'checkout',
userId,
cart,
result,
duration: '1.2s'
})// ❌ Useless error
throw new Error('Something went wrong')
// ❌ Missing context
throw new Error('Payment failed')import { createError } from 'evlog'
// ✅ Self-documenting error
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different payment method or contact your bank',
link: 'https://docs.example.com/payments/declined',
cause: originalError,
})// server/api/orders.post.ts
// ❌ No way to correlate logs
export default defineEventHandler(async (event) => {
console.log('Processing request')
const user = await getUser(event)
console.log('Got user', user.id)
// ...
})// server/api/orders.post.ts
// useLogger is auto-imported in Nuxt - no import needed!
// ✅ Request-scoped with full context
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const user = await getUser(event)
log.set({ user: { id: user.id, plan: user.plan } })
// ... do work, accumulate context ...
// emit() called automatically
})// scripts/process-job.ts
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ jobId: job.id, type: 'sync' })
log.set({ source: job.source, target: job.target })
// ... do work ...
log.emit() // Manual emit for standalone usagenpm install evlog// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
// Optional: only log specific routes (supports glob patterns)
include: ['/api/**'],
// Optional: send client logs to server (default: false)
transport: {
enabled: true,
},
},
})// nitro.config.ts
export default defineNitroConfig({
plugins: ['evlog/nitro'],
})throw createError({ message: 'Database connection failed', status: 500 })throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
})throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer - insufficient funds',
fix: 'Please use a different payment method or contact your bank',
link: 'https://docs.example.com/payments/declined',
})parseError()import { createError, parseError } from 'evlog'
// Backend - just throw the error
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined',
fix: 'Try another card',
link: 'https://docs.example.com/payments',
})
// Frontend - use parseError() for direct access
try {
await $fetch('/api/checkout')
} catch (err) {
const error = parseError(err)
// Direct access: error.message, error.why, error.fix, error.link
toast.add({
title: error.message,
description: error.why,
color: 'error',
actions: error.link
? [{ label: 'Learn more', onClick: () => window.open(error.link) }]
: undefined,
})
if (error.fix) console.info(`💡 Fix: ${error.fix}`)
}log// In Vue components, composables, or client-side code
log.info('checkout', 'User initiated checkout')
log.error({ action: 'payment', error: 'validation_failed' })
log.warn('form', 'Invalid email format')
log.debug({ component: 'CartDrawer', itemCount: 3 })// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
transport: {
enabled: true, // Send client logs to server
},
},
})/api/_evlog/ingestevlog:drainsource: 'client'nitroApp.hooks.hook('evlog:drain', async (ctx) => {
if (ctx.event.source === 'client') {
// Handle client logs specifically
}
})| Adapter | Import | Use Case |
|---|---|---|
| Axiom | | Axiom datasets for querying and dashboards |
| OTLP | | OpenTelemetry for Grafana, Datadog, Honeycomb, etc. |
// server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})NUXT_AXIOM_TOKENNUXT_AXIOM_DATASET// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})NUXT_OTLP_ENDPOINTimport { createAxiomDrain } from 'evlog/axiom'
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
const axiom = createAxiomDrain()
const otlp = createOTLPDrain()
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
await Promise.allSettled([axiom(ctx), otlp(ctx)])
})
})export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
// ctx.event contains the full wide event
await fetch('https://your-service.com/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(ctx.event),
})
})
})| Category | Examples | Risk |
|---|---|---|
| Credentials | Passwords, API keys, tokens | Account compromise |
| Payment data | Full card numbers, CVV | PCI violation |
| Personal data (PII) | SSN, unmasked emails | GDPR/CCPA violation |
| Authentication | Session tokens, JWTs | Session hijacking |
// ❌ DANGEROUS - logs everything including password
const body = await readBody(event)
log.set({ user: body })
// ✅ SAFE - explicitly select fields
log.set({
user: {
id: body.id,
plan: body.plan,
// password: body.password ← NEVER include
},
})// server/utils/sanitize.ts
export function maskEmail(email: string): string {
const [local, domain] = email.split('@')
return `${local[0]}***@${domain}`
}
export function maskCard(card: string): string {
return `****${card.slice(-4)}`
}useLogger(event).set()statuswhyfixlinkcreateError()useLogger(event)createRequestLogger()log.set()emit()logtransport.enabled: trueevlog/axiomevlog/otlp