review-logging-patterns

Structured logging for TypeScript/JavaScript with framework-agnostic setup, wide events, and drain adapters. Supports 12+ frameworks: Nuxt, Next.js, SvelteKit, Nitro, TanStack Start, NestJS, Express, Hono, Fastify, Elysia, Cloudflare Workers, and standalone TypeScript Detects and refactors console.log spam and unstructured errors into self-documenting wide events with grouped context Includes drain adapters for Axiom, OTLP, PostHog, Sentry, Better Stack, and file system; supports batching, sampling, and enrichment Built-in AI SDK integration captures token usage, tool calls, and streaming metrics from Vercel AI SDK automatically

INSTALLATION
npx skills add https://github.com/hugorcd/evlog --skill review-logging-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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

Wide events patterns

references/wide-events.md

Error handling

references/structured-errors.md

Code review checklist

references/code-review.md

Drain pipeline

references/drain-pipeline.md

Installation

npm install evlog

Framework Setup

Nuxt

// nuxt.config.ts

export default defineNuxtConfig({

  modules: ['evlog/nuxt'],

  evlog: {

    env: { service: 'my-app' },

    include: ['/api/**'],

  },

})

All evlog functions (useLogger, createError, parseError, log) are auto-imported — no import statements needed.

// server/api/checkout.post.ts — no imports needed

export default defineEventHandler(async (event) => {

  const log = useLogger(event)

  log.set({ user: { id: user.id, plan: user.plan } })

  return { success: true }

})

Drain, enrich, and tail sampling use Nitro hooks in server plugins:

// server/plugins/evlog-drain.ts

import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {

  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())

})

Client transport (auto-configured Vue plugin):

// nuxt.config.ts

evlog: {

  transport: { enabled: true },  // logs sent to /api/_evlog/ingest

}

Client-side: log, setIdentity, clearIdentity are auto-imported in components.

Next.js

Step 1: Create central config — all exports come from here:

// lib/evlog.ts

import type { DrainContext } from 'evlog'

import { createEvlog } from 'evlog/next'

import { createUserAgentEnricher, createRequestSizeEnricher } from 'evlog/enrichers'

import { createDrainPipeline } from 'evlog/pipeline'

const enrichers = [createUserAgentEnricher(), createRequestSizeEnricher()]

const pipeline = createDrainPipeline<DrainContext>({ batch: { size: 50, intervalMs: 5000 } })

const drain = pipeline(createAxiomDrain({ dataset: 'logs', token: process.env.AXIOM_TOKEN! }))

export const { withEvlog, useLogger, log, createError } = createEvlog({

  service: 'my-app',

  sampling: {

    rates: { info: 10 },

    keep: [{ status: 400 }, { duration: 1000 }],

  },

  routes: {

    '/api/auth/**': { service: 'auth-service' },

    '/api/checkout/**': { service: 'checkout-service' },

  },

  keep: (ctx) => {

    const user = ctx.context.user as { premium?: boolean } | undefined

    if (user?.premium) ctx.shouldKeep = true

  },

  enrich: (ctx) => {

    for (const enricher of enrichers) enricher(ctx)

  },

  drain,

})

Step 2: Wrap route handlers with withEvlog():

// app/api/checkout/route.ts

import { withEvlog, useLogger } from '@/lib/evlog'

export const POST = withEvlog(async (request: Request) => {

  const log = useLogger()  // Zero arguments — uses AsyncLocalStorage

  log.set({ user: { id: 'user_123', plan: 'enterprise' } })

  log.set({ cart: { items: 3, total: 14999 } })

  return Response.json({ success: true })

})

Step 3: Server Actions — same withEvlog() wrapper:

// app/actions.ts

'use server'

import { withEvlog, useLogger } from '@/lib/evlog'

export const checkout = withEvlog(async (formData: FormData) => {

  const log = useLogger()

  log.set({ action: 'checkout', source: 'server-action' })

  return { success: true }

})

Step 4: Middleware (optional — sets x-request-id + timing headers):

// proxy.ts

import { evlogMiddleware } from 'evlog/next'

export const proxy = evlogMiddleware()

export const config = { matcher: ['/api/:path*'] }

Step 5: Client Provider — wrap root layout:

// app/layout.tsx

import { EvlogProvider } from 'evlog/next/client'

export default function Layout({ children }: { children: React.ReactNode }) {

  return (

    <html lang="en">

      <body>

        <EvlogProvider service="my-app" transport={{ enabled: true, endpoint: '/api/evlog/ingest' }}>

          {children}

        </EvlogProvider>

      </body>

    </html>

  )

}

Step 6: Client logging — in any client component:

'use client'

import { log, setIdentity, clearIdentity } from 'evlog/next/client'

setIdentity({ userId: 'usr_123' })

log.info({ action: 'checkout_click' })

clearIdentity()

Step 7 (optional): Instrumentation — startup + 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:

// app/api/evlog/ingest/route.ts

import { NextRequest } from 'next/server'

const VALID_LEVELS = ['info', 'error', 'warn', 'debug'] as const

export async function POST(request: NextRequest) {

  const origin = request.headers.get('origin')

  const host = request.headers.get('host')

  if (origin &#x26;&#x26; new URL(origin).host !== host) {

    return Response.json({ error: 'Invalid origin' }, { status: 403 })

  }

  const body = await request.json()

  if (!body?.timestamp || !body?.level || !VALID_LEVELS.includes(body.level)) {

    return Response.json({ error: 'Invalid payload' }, { status: 400 })

  }

  const { service: _, ...sanitized } = body

  console.log('[CLIENT LOG]', JSON.stringify({ ...sanitized, service: 'my-app', source: 'client' }))

  return new Response(null, { status: 204 })

}

SvelteKit

// src/hooks.server.ts

import { initLogger } from 'evlog'

import { createEvlogHooks } from 'evlog/sveltekit'

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

export const { handle, handleError } = createEvlogHooks()

Access the logger via event.locals.log in route handlers or useLogger() from anywhere in the call stack:

// src/routes/api/users/[id]/+server.ts

import { json } from '@sveltejs/kit'

export const GET = ({ locals, params }) => {

  locals.log.set({ user: { id: params.id } })

  return json({ id: params.id })

}
import { useLogger } from 'evlog/sveltekit'

async function findUsers() {

  const log = useLogger()

  log.set({ db: { query: 'SELECT * FROM users' } })

}

Full pipeline with drain, enrich, and tail sampling:

import { createAxiomDrain } from 'evlog/axiom'

export const { handle, handleError } = createEvlogHooks({

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

})

Nitro v3

// nitro.config.ts

import { defineConfig } from 'nitro'

import evlog from 'evlog/nitro/v3'

export default defineConfig({

  modules: [evlog({ env: { service: 'my-api' } })],

})
// routes/api/checkout.post.ts

import { defineHandler } from 'nitro/h3'

import { useLogger } from 'evlog/nitro/v3'

export default defineHandler(async (event) => {

  const log = useLogger(event)

  log.set({ action: 'checkout' })

  return { ok: true }

})

TanStack Start

TanStack Start uses Nitro v3. Install evlog and add a nitro.config.ts:

// nitro.config.ts

import { defineConfig } from 'nitro'

import evlog from 'evlog/nitro/v3'

export default defineConfig({

  experimental: { asyncContext: true },

  modules: [evlog({ env: { service: 'my-app' } })],

})

Add the error handling middleware to __root.tsx:

// src/routes/__root.tsx

import { createMiddleware } from '@tanstack/react-start'

import { evlogErrorHandler } from 'evlog/nitro/v3'

export const Route = createRootRoute({

  server: {

    middleware: [createMiddleware().server(evlogErrorHandler)],

  },

})

Use useRequest() from nitro/context to access the logger:

import { useRequest } from 'nitro/context'

import type { RequestLogger } from 'evlog'

const req = useRequest()

const log = req.context.log as RequestLogger

log.set({ user: { id: 'user_123' } })

Nitro v2

// nitro.config.ts

import { defineNitroConfig } from 'nitropack/config'

import evlog from 'evlog/nitro'

export default defineNitroConfig({

  modules: [evlog({ env: { service: 'my-api' } })],

})

Import useLogger from evlog/nitro in routes.

NestJS

// src/app.module.ts

import { Module } from '@nestjs/common'

import { EvlogModule } from 'evlog/nestjs'

@Module({

  imports: [EvlogModule.forRoot()],

})

export class AppModule {}

EvlogModule.forRoot() registers a global middleware. Use useLogger() to access the request-scoped logger from any controller or service:

import { useLogger } from 'evlog/nestjs'

async function findUsers() {

  const log = useLogger()

  log.set({ db: { query: 'SELECT * FROM users' } })

}

Full pipeline with drain, enrich, and tail sampling:

import { createAxiomDrain } from 'evlog/axiom'

EvlogModule.forRoot({

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

})

For async configuration with NestJS DI, use forRootAsync():

EvlogModule.forRootAsync({

  imports: [ConfigModule],

  inject: [ConfigService],

  useFactory: (config) => ({

    drain: createAxiomDrain({ token: config.get('AXIOM_TOKEN') }),

  }),

})

Express

import express from 'express'

import { initLogger } from 'evlog'

import { evlog, useLogger } from 'evlog/express'

initLogger({ env: { service: 'my-api' } })

const app = express()

app.use(evlog())

app.get('/api/users', (req, res) => {

  req.log.set({ users: { count: 42 } })

  res.json({ users: [] })

})

Use useLogger() to access the logger from anywhere in the call stack without passing req:

import { useLogger } from 'evlog/express'

async function findUsers() {

  const log = useLogger()

  log.set({ db: { query: 'SELECT * FROM users' } })

}

Full pipeline with drain, enrich, and tail sampling:

import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

}))

Hono

import { Hono } from 'hono'

import { initLogger } from 'evlog'

import { evlog, type EvlogVariables } from 'evlog/hono'

initLogger({ env: { service: 'my-api' } })

const app = new Hono<EvlogVariables>()

app.use(evlog())

app.get('/api/users', (c) => {

  const log = c.get('log')

  log.set({ users: { count: 42 } })

  return c.json({ users: [] })

})

Access the logger via c.get('log') in handlers. No useLogger() — use c.get('log') and pass it down explicitly, or use Express/Fastify/Elysia if you need useLogger() across async boundaries.

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:

import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

}))

Fastify

import Fastify from 'fastify'

import { initLogger } from 'evlog'

import { evlog, useLogger } from 'evlog/fastify'

initLogger({ env: { service: 'my-api' } })

const app = Fastify({ logger: false })

await app.register(evlog)

app.get('/api/users', async (request) => {

  request.log.set({ users: { count: 42 } })

  return { users: [] }

})

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:

import { createAxiomDrain } from 'evlog/axiom'

await app.register(evlog, {

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

})

Elysia

import { Elysia } from 'elysia'

import { initLogger } from 'evlog'

import { evlog, useLogger } from 'evlog/elysia'

initLogger({ env: { service: 'my-api' } })

const app = new Elysia()

  .use(evlog())

  .get('/api/users', ({ log }) => {

    log.set({ users: { count: 42 } })

    return { users: [] }

  })

  .listen(3000)

Use useLogger() to access the logger from anywhere in the call stack:

import { useLogger } from 'evlog/elysia'

async function findUsers() {

  const log = useLogger()

  log.set({ db: { query: 'SELECT * FROM users' } })

}

Full pipeline with drain, enrich, and tail sampling:

import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({

  include: ['/api/**'],

  drain: createAxiomDrain(),

  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

  keep: (ctx) => {

    if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

  },

}))

React Router

// react-router.config.ts

import type { Config } from '@react-router/dev/config'

export default {

  future: {

    v8_middleware: true,

  },

} satisfies Config
// app/root.tsx

import { initLogger } from 'evlog'

import { evlog } from 'evlog/react-router'

initLogger({ env: { service: 'my-api' } })

export const middleware: Route.MiddlewareFunction[] = [

  evlog(),

]

Access the logger via context.get(loggerContext) in loaders and actions:

// app/routes/api.users.$id.tsx

import { loggerContext } from 'evlog/react-router'

export async function loader({ params, context }: Route.LoaderArgs) {

  const log = context.get(loggerContext)

  log.set({ user: { id: params.id } })

  return { users: [] }

}

Use useLogger() to access the logger from anywhere in the call stack without passing context:

import { useLogger } from 'evlog/react-router'

async function findUsers() {

  const log = useLogger()

  log.set({ db: { query: 'SELECT * FROM users' } })

}

Full pipeline with drain, enrich, and tail sampling:

import { createAxiomDrain } from 'evlog/axiom'

export const middleware: Route.MiddlewareFunction[] = [

  evlog({

    include: ['/api/**'],

    drain: createAxiomDrain(),

    enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },

    keep: (ctx) => {

      if (ctx.duration &#x26;&#x26; ctx.duration > 2000) ctx.shouldKeep = true

    },

  }),

]

Cloudflare Workers

import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'

initWorkersLogger({ env: { service: 'edge-api' } })

export default {

  async fetch(request: Request) {

    const log = createWorkersLogger(request)

    try {

      log.set({ route: 'health' })

      const response = new Response('ok', { status: 200 })

      log.emit({ status: response.status })

      return response

    } catch (error) {

      log.error(error as Error)

      log.emit({ status: 500 })

      throw error

    }

  },

}

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:

// vite.config.ts

import evlog from 'evlog/vite'

export default defineConfig({

  plugins: [

    evlog({

      service: 'my-app',

      autoImports: true,           // auto-import log, createEvlogError, parseError

      strip: ['debug'],            // remove log.debug() in production

      sourceLocation: true,        // inject file:line in dev + prod

      client: {                    // client-side logging

        transport: { endpoint: '/api/logs' },

      },

    }),

  ],

})

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

import { initLogger, createRequestLogger } from 'evlog'

initLogger({ env: { service: 'my-worker', environment: 'production' } })

const log = createRequestLogger({ jobId: job.id })

log.set({ source: job.source, recordsSynced: 150 })

log.emit()  // Manual emit required in standalone

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

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

--

Custom tail sampling callback (Next.js)

Nitro Hooks (Nuxt, Nitro v2/v3)

Hook

When

Use

evlog:drain

After enrichment

Send events to external services

evlog:enrich

After emit, before drain

Add derived context

evlog:emit:keep

During emit

Custom tail sampling logic

close

Server shutdown

Flush drain pipeline buffers

Drain Adapters

Adapter

Import

Env Vars

Axiom

evlog/axiom

AXIOM_TOKEN, AXIOM_DATASET

OTLP

evlog/otlp

OTLP_ENDPOINT (or OTEL_EXPORTER_OTLP_ENDPOINT)

HyperDX

evlog/hyperdx

HYPERDX_API_KEY (optional HYPERDX_OTLP_ENDPOINT; defaults to https://in-otel.hyperdx.io)

PostHog

evlog/posthog

POSTHOG_API_KEY, POSTHOG_HOST

Sentry

evlog/sentry

SENTRY_DSN

Better Stack

evlog/better-stack

BETTER_STACK_SOURCE_TOKEN

File System

evlog/fs

None (local file system)

In Nuxt/Nitro, use the NUXT_ prefix (e.g., NUXT_AXIOM_TOKEN) so values are available via useRuntimeConfig(). All adapters also read unprefixed variables as fallback.

Setup pattern per framework:

// Nuxt/Nitro: server/plugins/evlog-drain.ts

import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {

  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())

})

// Hono / Express / Elysia: pass drain in middleware options

import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({ drain: createAxiomDrain() }))

// Fastify: pass drain in plugin options

import { createAxiomDrain } from 'evlog/axiom'

await app.register(evlog, { drain: createAxiomDrain() })

// NestJS: pass drain in module options

import { createAxiomDrain } from 'evlog/axiom'

EvlogModule.forRoot({ drain: createAxiomDrain() })

// Next.js: pass drain to createEvlog()

import { createAxiomDrain } from 'evlog/axiom'

import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({ batch: { size: 50 } })

const drain = pipeline(createAxiomDrain())

// then: createEvlog({ ..., drain })

// Standalone: pass drain to initLogger()

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

See references/drain-pipeline.md for batching, retry, and buffer overflow config.

Enrichers

Built-in: createUserAgentEnricher(), createGeoEnricher(), createRequestSizeEnricher(), createTraceContextEnricher() — all from evlog/enrichers.

// 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

  },

})

AI SDK Integration

Capture token usage, tool calls, model info, and streaming metrics from the Vercel AI SDK into wide events. Import from evlog/ai. Requires ai >= 6.0.0 as a peer dependency.

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,

  onFinish: ({ text }) => {

    // User callbacks remain free — no conflict

  },

})

ai.wrap() uses model middleware to transparently capture all LLM calls. Works with generateText, streamText, generateObject, streamObject, and ToolLoopAgent.

For embeddings (different model type):

const { embedding, usage } = await embed({ model: embeddingModel, value: query })

ai.captureEmbed({ usage })

Wide event ai field includes: calls, model, provider, inputTokens, outputTokens, totalTokens, cacheReadTokens, reasoningTokens, finishReason, toolCalls, steps, msToFirstChunk, msToFinish, tokensPerSecond, error.

Anti-patterns to detect:

Anti-Pattern

Fix

Manual token tracking in onFinish

ai.wrap() — middleware captures automatically

console.log('tokens:', result.usage)

ai.wrap() — structured ai.* fields in wide event

No AI observability

Add createAILogger(log) + ai.wrap()

Structured Errors

import { createError } from 'evlog'  // or auto-imported in Nuxt

// Minimal

throw createError({ message: 'Database connection failed', status: 500 })

// Standard

throw createError({ message: 'Payment failed', status: 402, why: 'Card declined by issuer' })

// Complete

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',

  cause: originalError,

})

Frontend — extract all fields with parseError():

import { parseError } from 'evlog'

const error = parseError(err)

// error.message, error.status, error.why, error.fix, error.link

See references/structured-errors.md for common patterns and templates.

Anti-Patterns to Detect

Anti-Pattern

Fix

Multiple console.log in one function

Single wide event with log.set()

throw new Error('...')

throw createError({ message, status, why, fix })

console.error(e); throw e

log.error(e); throw createError(...)

No logging in request handlers

Add useLogger(event) / useLogger() / createRequestLogger()

Flat log data { uid, n, t }

Grouped objects: { user: {...}, cart: {...} }

Logging sensitive data log.set({ user: body })

Explicit fields: { user: { id: body.id, plan: body.plan } }

See references/code-review.md for the full checklist.

Loading Reference Files

Load based on what you're working on — do not load all at once:

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card