Logging

Simple Logging

Structured logging for everyday use. Replace console.log with log.info, log.error, log.warn, and log.debug. Fire-and-forget events with pretty output in dev and JSON in production.

The log API is the simplest way to use evlog. Each call emits a single structured event, no accumulation, no lifecycle management, no manual emit().

In Nuxt, log is auto-imported. No import statement needed.

Setup

For standalone projects (non-Nuxt), initialize once at startup:

src/index.ts
import { initLogger, log } from 'evlog'

initLogger({
  env: { service: 'my-app' },
})

log.info('app', 'Server started')
env.service defaults to 'app' if not specified. Only set it if you want a custom service name.

Two Call Styles

Tagged Logs

Pass a tag and a message for quick, readable output:

src/index.ts
import { log } from 'evlog'

log.info('auth', 'User logged in')
log.warn('cache', 'Cache miss for key user:42')
log.error('payment', 'Stripe webhook failed')
log.debug('router', 'Matched route /api/checkout')
Output (Pretty)
10:23:45.612 [auth] User logged in
10:23:45.613 [cache] Cache miss for key user:42
10:23:45.614 ERROR [payment] Stripe webhook failed
10:23:45.615 [router] Matched route /api/checkout

Structured Events

Pass an object for rich, queryable events that flow through the drain pipeline:

src/index.ts
import { log } from 'evlog'

log.info({ action: 'user_login', userId: 42, method: 'oauth', provider: 'github' })
log.error({ action: 'sync_failed', source: 'postgres', target: 's3', error: 'connection_timeout' })
Output (Pretty)
10:23:45.612 INFO [my-app]
  ├─ action: user_login
  ├─ userId: 42
  ├─ method: oauth
  └─ provider: github
Tagged logs are optimized for console readability. Structured events (object form) produce full wide events that flow through the drain pipeline to external services.

Log Levels

LevelMethodWhen to use
infolog.info()Normal operations: startup, shutdown, successful actions
warnlog.warn()Unexpected but recoverable situations: cache miss, retry, deprecation
errorlog.error()Failures that need attention: API errors, timeouts, invalid state
debuglog.debug()Development-only details: SQL queries, intermediate state, routing
log.debug() calls can be stripped from production builds using the Vite Plugin or the Nuxt module's strip option.

Common Patterns

Application Lifecycle

src/index.ts
import { log } from 'evlog'

log.info('app', 'Starting server on port 3000')
log.info({ action: 'db_connected', host: 'localhost', database: 'mydb', pool: 10 })
log.info('app', 'Ready to accept connections')

Background Tasks

src/jobs/cleanup.ts
import { log } from 'evlog'

log.info({ action: 'cron_started', job: 'cleanup', schedule: '0 */6 * * *' })
log.info({ action: 'cron_completed', job: 'cleanup', deleted: 42, duration: 1200 })

Utility Functions

src/utils/webhook.ts
import { log } from 'evlog'

function processWebhook(payload: WebhookPayload) {
  log.info({ action: 'webhook_received', type: payload.type, source: payload.source })

  if (!isValid(payload)) {
    log.warn({ action: 'webhook_invalid', type: payload.type, reason: 'missing_signature' })
    return
  }
}

Drain Integration

When using the object form, events are sent through the drain pipeline just like wide events:

src/index.ts
import { initLogger, log } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

initLogger({
  env: { service: 'my-app' },
  drain: createAxiomDrain(),
})

log.info({ action: 'deploy', version: '1.2.3', region: 'us-east-1' })

When to Upgrade to createLogger

Use log when each event is self-contained. When you need to accumulate context across multiple steps of an operation, switch to createLogger:

scripts/sync-data.ts
import { log, createLogger } from 'evlog'

// log: each call is independent
log.info({ action: 'sync_started', source: 'postgres' })
log.info({ action: 'sync_completed', records: 150 })

// createLogger: accumulate context, emit once
const syncLog = createLogger({ source: 'postgres' })
syncLog.set({ records: 150 })
syncLog.set({ status: 'complete' })
syncLog.emit()

Next Steps