Agent skill

Review Logging Patterns

by evloghq in evloghq/evlog

Review code for logging patterns and suggest evlog adoption.

MITAuto-check passedDatabases

Install Review Logging Patterns

skills CLI
$ npx skills add evloghq/evlog --skill review-logging-patterns -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install evloghq/evlog review-logging-patterns --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/evloghq/evlog.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/review-logging-patterns .claude/skills/review-logging-patterns && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
review-logging-patterns
GitHub stars
1.9k
Token cost
~11k tokens
SKILL.md length
2,313 words
Files
5 (incl. references)
Skills in repo
20
Repo updated
First seen
Licence
MIT

At a glance

Review code for logging patterns and suggest evlog adoption.

  • Works in 3 steps: Setup: evlog init → Score: evlog map → Lock it in CI: --min-score and --baseline
  • Tasks that involve Code review
  • SKILL.md covers When to Use, Quick Reference, Audit logs and Installation, plus 3 more sections
  • Calls npx, pnpm and npm; reaches in-otel.hyperdx.io; needs AXIOM_API_KEY and BETTER_STACK_API_KEY

What it does

Review Logging Patterns is an agent skill from evloghq/evlog. Review code for logging patterns and suggest evlog adoption. Optionally use the evlog CLI, shipped with the evlog package (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, Hono, Express, and Fastify. Guides setup on those plus SvelteKit, React Router, NestJS, Express, Fastify, Elysia, oRPC, Cloudflare Workers, AWS Lambda, Astro, and standalone TypeScript. Detects…

Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including reference files (for example `references/code-review.md`, `references/drain-pipeline.md` and `references/structured-errors.md`).

It sits in Databases, covering Code review, Static sites and blogs and Data warehousing. It works with Fastify, Nuxt, Hono and Next.js. The repository describes itself as: Digging through logs is not observability. It's hope — wide events, structured errors, TypeScript-first, every runtime. The licence is MIT.

When your agent uses it

  • Tasks that involve Code review
  • Tasks that involve Static sites and blogs
  • Tasks that involve Data warehousing

Example prompts

  • “/review-logging-patterns”

Requirements

  • Node.js
  • A credential in AXIOM_API_KEY

Workflow steps

3 steps, taken from the step headings in SKILL.md.

  1. Setup: evlog init
  2. Score: evlog map
  3. Lock it in CI: --min-score and --baseline

What it can do on your machine

Read from SKILL.md and the folder at commit 59a105f. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npx
    • pnpm
    • npm
    • bunx

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • in-otel.hyperdx.io

    Also links to:

    • evlog.dev
    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • AXIOM_API_KEY
    • BETTER_STACK_API_KEY
    • HYPERDX_API_KEY
    • POSTHOG_API_KEY
    • DD_API_KEY
    • DATADOG_API_KEY
    • LOKI_API_KEY
    • CLICKHOUSE_PASSWORD

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Review Logging Patterns loads about 11k tokens when it runs, and up to ~20k if it reads all its reference files. Until then it costs about 202 tokens; SKILL.md has 2,313 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~202
When it runs · the whole SKILL.md, loaded when a task matches
~11k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~20k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from evloghq/evlog at commit 59a105f, republished under its MIT licence (© evloghq). 2,313 words, ~10,596 tokens.

Download SKILL.mdSave it as .claude/skills/review-logging-patterns/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.
name
review-logging-patterns
description
Review code for logging patterns and suggest evlog adoption. Optionally use the evlog CLI, shipped with the evlog package (`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, Hono, Express, and Fastify. 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
metadata.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
  • Improving error handling with better context
  • Configuring log draining, sampling, or enrichment

Quick Reference

Working on...Resource
Setup (CLI)evlog init: wire evlog into the project
Project conventions (CLI)evlog agents: write the evlog block into the project's AGENTS.md
Coverage map (CLI)evlog map: score dark entry points
CI gating (CLI)evlog map --min-score / --baseline: gate regressions
Wide events patternsreferences/wide-events.md
Error handlingreferences/structured-errors.md
Code review checklistreferences/code-review.md
Drain pipelinereferences/drain-pipeline.md
Audit logsbuild-audit-logs skill + docs

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).

typescript
log.audit({
  action: 'invoice.refund',
  actor: { type: 'user', id: user.id },
  target: { type: 'invoice', id: invoice.id },
  outcome: 'success',
})

Docs: https://www.evlog.dev/use-cases/audit/overview

Installation

bash
npm install evlog

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). 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:

bash
# preview everything without writing (always start here)
npx evlog init --dry-run --yes

# then apply — flags instead of prompts
npx evlog 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, express, fastify), --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

2. Score: evlog map
bash
npx evlog map --no-write
# agents: npx evlog map --json --no-write

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 (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:

bash
# in CI, after pnpm add -D @evlog/cli (pinned by the lockfile; ask first)
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, then re-run map without --baseline to accept an intentional change. Docs: https://www.evlog.dev/cli/ci

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
typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
    include: ['/api/**'],
  },
})

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.

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

typescript
// 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):

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

typescript
// 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', apiKey: process.env.AXIOM_API_KEY! }))

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():

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

typescript
// 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):

typescript
// proxy.ts
import { evlogMiddleware } from 'evlog/next'
export const proxy = evlogMiddleware()
export const config = { matcher: ['/api/:path*'] }

Step 5: Client Provider. Wrap the root layout:

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

tsx
'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 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:

typescript
// 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 && 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
typescript
// 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:

typescript
// 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 })
}
typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
})
Nitro v3
typescript
// nitro.config.ts
import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  modules: [evlog({ env: { service: 'my-api' } })],
})
typescript
// 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:

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

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

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

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
})

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

typescript
EvlogModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config) => ({
    drain: createAxiomDrain({ apiKey: config.get('AXIOM_API_KEY') }),
  }),
})
Express
typescript
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:

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))
Hono
typescript
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. Use useLogger() from evlog/hono in the layers underneath (services, repositories) where c is not in hand. Both return the same logger:

typescript
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).

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))
Fastify
typescript
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:

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
})
Elysia
typescript
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:

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))
React Router
typescript
// react-router.config.ts
import type { Config } from '@react-router/dev/config'

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config
typescript
// 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:

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

typescript
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:

typescript
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 && ctx.duration > 2000) ctx.shouldKeep = true
    },
  }),
]
oRPC
typescript
import { os } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { initLogger } from 'evlog'
import { evlog, withEvlog, type EvlogOrpcContext } from 'evlog/orpc'

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

const base = os.$context<EvlogOrpcContext>().use(evlog())

const router = {
  ping: base.handler(({ context }) => {
    context.log.set({ pinged: true })
    return { ok: true }
  }),
}

const handler = withEvlog(new RPCHandler(router))

export default async function fetch(request: Request) {
  const { matched, response } = await handler.handle(request, { prefix: '/rpc' })
  return matched ? response : new Response('Not Found', { status: 404 })
}

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:

typescript
import { useLogger } from 'evlog/orpc'

async function chargeCard(amount: number) {
  const log = useLogger()
  log.set({ payment: { amount } })
}

Full pipeline with drain, enrich, and tail sampling:

typescript
import { createAxiomDrain } from 'evlog/axiom'

const handler = withEvlog(new RPCHandler(router), {
  include: ['/rpc/**'],
  drain: createAxiomDrain(),
  enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },
  keep: (ctx) => {
    if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
  },
})
Cloudflare Workers
typescript
import { initWorkersLogger, withEvlog } from 'evlog/workers'

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

export default withEvlog(async (request, _env, _ctx, log) => {
  log.set({ action: 'handle_request' })
  return Response.json({ ok: true })
})

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).

typescript
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()
    }
  }
}
Astro
typescript
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware'
import { initLogger, createRequestLogger } from 'evlog'

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

export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
  const url = new URL(request.url)
  const log = createRequestLogger({ method: request.method, path: url.pathname })
  locals.log = log

  try {
    const response = await next()
    log.emit()
    return response
  } catch (error) {
    log.error(error instanceof Error ? error : new Error(String(error)))
    log.emit()
    throw error
  }
})

Type locals.log in src/env.d.ts (interface Locals { log: RequestLogger }). Pair with the Vite plugin (below) for auto-imports and build-time DX.

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:

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

Show full SKILL.md (941 more words)Show less

Configuration Options

All options work in Nuxt (evlog key), Nitro (passed to evlog()), Next.js (createEvlog()), and standalone (initLogger()).

OptionTypeDefaultDescription
env.service / servicestring'app'Service name in logs
enabledbooleantrueGlobal toggle (no-ops when false)
prettybooleantrue in devPretty tree format vs JSON
silentbooleanfalseSuppress console output. Events still go to drains
includestring[]All routesRoute glob patterns to log
excludestring[]NoneRoute patterns to exclude (takes precedence)
routesRecord<string, { service }>--Route-specific service names
minLevel'debug' | 'info' | 'warn' | 'error''debug'Hard threshold for the global log API and client log (not request wide events). Use sampling.rates for probabilistic volume on requests
sampling.ratesobject--Head sampling: { info: 10, warn: 50 } (0-100%)
sampling.keeparray--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)
redactboolean | RedactConfigtrue in productionEnabled by default in production. false to disable. Object for fine-grained control
Nitro Hooks (Nuxt, Nitro v2/v3)
HookWhenUse
evlog:drainAfter enrichmentSend events to external services
evlog:enrichAfter emit, before console output and drainAdd derived context
evlog:emit:keepDuring emitCustom tail sampling logic
closeServer shutdownFlush drain pipeline buffers

Drain Adapters

AdapterImportEnv Vars
Axiomevlog/axiomAXIOM_API_KEY, AXIOM_DATASET
OTLPevlog/otlpOTLP_ENDPOINT (or the standard OTEL_EXPORTER_OTLP_[LOGS_]ENDPOINT, _HEADERS, _COMPRESSION, _PROTOCOL, OTEL_RESOURCE_ATTRIBUTES). protocol: 'http/protobuf' sends binary protobuf. semanticConventions: true adds OTel attribute names (http.*, exception.*, gen_ai.*)
HyperDXevlog/hyperdxHYPERDX_API_KEY (optional HYPERDX_OTLP_ENDPOINT; defaults to https://in-otel.hyperdx.io)
PostHogevlog/posthogPOSTHOG_API_KEY, POSTHOG_HOST
Sentryevlog/sentrySENTRY_DSN
Better Stackevlog/better-stackBETTER_STACK_API_KEY
Datadogevlog/datadogDD_API_KEY or DATADOG_API_KEY, optional DD_SITE / DATADOG_LOGS_URL
Grafana Lokievlog/lokiLOKI_ENDPOINT, optional LOKI_API_KEY + LOKI_USER (Grafana Cloud) or LOKI_TENANT_ID (multi-tenant)
ClickHouseevlog/clickhouseCLICKHOUSE_ENDPOINT, optional CLICKHOUSE_USER / CLICKHOUSE_PASSWORD / CLICKHOUSE_DATABASE / CLICKHOUSE_TABLE
File Systemevlog/fsNone (local file system)
Memoryevlog/memoryNone (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/httpNone (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.

Setup pattern per framework:

typescript
// 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. Each accepts { overwrite?: boolean } (default false). Use createDefaultEnrichers() to compose all four in one call:

typescript
import { createDefaultEnrichers } from 'evlog/enrichers'
app.use(evlog({ enrich: createDefaultEnrichers() }))
typescript
// 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.

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

PatternExample InputMasked Output
creditCard4111111111111111****1111
emailalice@example.coma***@***.com
ipv4192.168.1.100***.***.***.100
phone+33 6 12 34 56 78+33 ****5678
jwteyJhbGciOi...eyJ***.***
bearerBearer sk_live_abc...Bearer ***
ibanFR76 3000 6000 ...189FR76****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)
typescript
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():

typescript
import { createAILogger, createEvlogIntegration } from 'evlog/ai'

const ai = createAILogger(log)

const agent = new ToolLoopAgent({
  model: ai.wrap('anthropic/claude-sonnet-4.6'),
  tools: { searchWeb, queryDatabase },
  stopWhen: stepCountIs(5),
  telemetry: {
    integrations: [createEvlogIntegration(ai)],
  },
})

This adds ai.tools (per-tool { name, durationMs, success, error? }) and ai.totalDurationMs to the wide event.

Embeddings
typescript
const { embedding, usage } = await embed({ model: embeddingModel, value: query })
ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })

For embedMany, pass the batch count:

typescript
ai.captureEmbed({ usage, model: 'text-embedding-3-small', count: documents.length })
Cost estimation

Pass a pricing map to get ai.estimatedCost in the wide event:

typescript
const ai = createAILogger(log, {
  cost: {
    'claude-sonnet-4.6': { input: 3, output: 15 },
    'gpt-4o': { input: 2.5, output: 10 },
  },
})
Wide event ai field

Includes: calls, model, provider, inputTokens, outputTokens, totalTokens, cacheReadTokens, reasoningTokens, finishReason, toolCalls, steps, msToFirstChunk, msToFinish, tokensPerSecond, error, tools (via telemetry integration), totalDurationMs (via telemetry integration), embedding (via captureEmbed), estimatedCost (via cost option).

Anti-patterns to detect:

Anti-PatternFix
Manual token tracking in onFinishai.wrap(): middleware captures automatically
console.log('tokens:', result.usage)ai.wrap(): structured ai.* fields in wide event
No AI observabilityAdd createAILogger(log) + ai.wrap()
No tool execution timingAdd createEvlogIntegration(ai) to telemetry.integrations
Manual cost calculationUse cost option in createAILogger()

Recommend these when the review surfaces the matching need. Each has full docs on evlog.dev:

Need spottedevlog answerDocs
Auth'd user identity missing from events (Better Auth apps)evlog/better-auth: identifyUser(), createAuthMiddleware(), client identity synchttps://www.evlog.dev/use-cases/better-auth/overview
Ad-hoc field names drifting across the codebaseTyped fields + error/audit catalogs (evlog/catalog)https://www.evlog.dev/learn/typed-fields · https://www.evlog.dev/learn/catalogs
Cross-cutting hooks (request start/finish, client logs, logger extension)Plugins: definePluginhttps://www.evlog.dev/extend/plugins
Tail logs live during dev / build a log viewercreateStreamDrain (evlog/stream, SSE) + readFsLogs / tailFsLogs (evlog/fs)https://www.evlog.dev/extend/stream
Agents need to query logs over HTTP in devMemory adapter + readMemoryLogs() behind a dev-only endpointhttps://www.evlog.dev/integrate/adapters/self-hosted/memory

Structured Errors

typescript
import { createError } from 'evlog'  // required in Nuxt too: a bare createError is h3's

// 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,
})

// Backend-only context (wide events / drains — never HTTP body or parseError())
throw createError({
  message: 'Not allowed',
  status: 403,
  why: 'Insufficient permissions',
  internal: { correlationId: 'req_abc', resourceId: 'proj_123' },
})

Frontend: extract user-facing fields with parseError() (internal is never returned to clients):

typescript
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-PatternFix
Multiple console.log in one functionSingle wide event with log.set()
throw new Error('...')throw createError({ message, status, why, fix })
console.error(e); throw elog.error(e); throw createError(...)
No logging in request handlersAdd 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 } } + enable redact: true
Putting support-only IDs in why / messageUse createError({ ..., internal: { ... } }) for non-user-facing diagnostics

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


Loading Reference Files

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

© evloghq, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 4 other files (references) in skills/review-logging-patterns of evloghq/evlog.

  • SKILL.md
  • references/code-review.md
  • references/drain-pipeline.md
  • references/structured-errors.md
  • references/wide-events.md

Open the folder on GitHubat commit 59a105f

Compare with similar skills

Review Logging Patterns next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Review Logging Patterns compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Review Logging Patterns this skillevloghq/evlog1.9k—~11kAutomated safety check: PassMIT
Better Autheinverne/dotfiles121—~4kAutomated safety check: NotesMIT
Review Logging Patternsactivepieces/activepieces25k—~8.2kAutomated safety check: PassMIT
Clerkgeekskai/blog1033 repos~1.5kAutomated safety check: PassMIT
Workflow Initusenotra/notra256—~821Automated safety check: PassAGPL-3.0
Inngest SetupAsymmetric-al/core381—~3.3kAutomated safety check: NotesAGPL-3.0

Similar skills

  • Better Auth

    einverne/dotfiles

    Guide for implementing Better Auth - a framework-agnostic authentication and authorization framework for TypeScript.

    121 GitHub stars~4k tokensUpdated 1 mo ago
    Backend & APIsAuto-check: notes
  • Review Logging Patterns

    activepieces/activepieces

    Review code for logging patterns and suggest evlog adoption.

    25k GitHub stars~8.2k tokensUpdated yesterday
    AI & LLM EngineeringAuto-check passed
  • Clerk

    geekskai/blog

    Clerk authentication router. An agent skill from geekskai/blog.

    103 GitHub starsUsed in 3 repos~1.5k tokens
    Backend & APIsAuto-check passed
  • Workflow Init

    usenotra/notra

    Install and configure Vercel Workflow SDK before it exists in nodemodules.

    256 GitHub stars~821 tokensUpdated yesterday
    Backend & APIsAuto-check passed
  • Inngest Setup

    Asymmetric-al/core

    A skill your agent uses when adding durable execution to a TypeScript project — building retry-safe webhook handlers, background jobs that survive crashes, scheduled tasks, or long-running workflows…

    381 GitHub stars~3.3k tokensUpdated yesterday
    Backend & APIsAuto-check: notes
  • Cloudflare Workers Frameworks

    secondsky/claude-skills

    Framework integration for Cloudflare Workers. An agent skill from secondsky/claude-skills.

    227 GitHub stars~1.4k tokensUpdated 10 days ago
    Frontend & DesignAuto-check passed

More from evloghq/evlog

All 20 skills in this repo
  • Walks through adding a new built-in evlog drain adapter for an observability platform: source, build config, exports, tests, docs and PR scope.

    1.9k GitHub stars~2.9k tokensUpdated yesterday
    Auto-check passed
  • Guides adding a new built-in enricher to the evlog package, covering the source, tests, docs, README, a related skill and a changeset.

    1.9k GitHub stars~1.7k tokensUpdated yesterday
    Auto-check passed
  • Walks a contributor through adding a new HTTP framework integration to the evlog logging package: middleware source, build entry, exports, tests, example app and docs.

    1.9k GitHub stars~5k tokensUpdated yesterday
    Auto-check: notes
  • Walks through adding a new rule or framework adapter to `evlog map` in @evlog/cli, from the rule source and registry to types, tests, docs and the published skill.

    1.9k GitHub stars~2.9k tokensUpdated yesterday
    Auto-check passed
  • Rules for writing and reviewing evlog docs, blog posts, READMEs, skills and AGENTS.md files, with separate review and rewrite roles, a house voice and a catalog of AI-sounding tells.

    1.9k GitHub stars~2.9k tokensUpdated yesterday
    Auto-check passed
  • Before After

    evloghq/evlog

    Produce a before/after visual comparison of an evlog surface (landing, docs, telemetry, playgrounds) and share it as public Blob URLs.

    1.9k GitHub stars~2.7k tokensUpdated yesterday
    Auto-check passed

Questions about Review Logging Patterns

What does Review Logging Patterns do?

Review code for logging patterns and suggest evlog adoption. Review Logging Patterns is an agent skill from evloghq/evlog. Review code for logging patterns and suggest evlog adoption.

When should I use Review Logging Patterns?

Review Logging Patterns fits situations like: tasks that involve Code review; tasks that involve Static sites and blogs; tasks that involve Data warehousing.

How do I install Review Logging Patterns in Claude Code?

Run `npx skills add evloghq/evlog --skill review-logging-patterns -a claude-code`. Or copy the skill folder (skills/review-logging-patterns in evloghq/evlog) into .claude/skills/review-logging-patterns in your project. Claude Code loads it when a task matches its description.

How do I install Review Logging Patterns in Codex?

Run `npx skills add evloghq/evlog --skill review-logging-patterns -a codex`. Or copy the skill folder (skills/review-logging-patterns in evloghq/evlog) into .agents/skills/review-logging-patterns in your project. Codex loads it when a task matches its description.

Can I use Review Logging Patterns in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add evloghq/evlog --skill review-logging-patterns -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/review-logging-patterns, .gemini/skills/review-logging-patterns, .github/skills/review-logging-patterns and .opencode/skills/review-logging-patterns in your project.

What does Review Logging Patterns need to run?

Going by SKILL.md and its folder, Review Logging Patterns needs the command-line tools its instructions call (npx, pnpm, npm and bunx) and credentials named AXIOM_API_KEY, BETTER_STACK_API_KEY, HYPERDX_API_KEY and POSTHOG_API_KEY. Our summary lists: Node.js; A credential in AXIOM_API_KEY.

Does Review Logging Patterns access the network?

SKILL.md names 3 domains. In commands or code: in-otel.hyperdx.io; the agent is likely to contact it when it follows the instructions. As links in the text: evlog.dev and github.com. This is read from the text; nothing was executed.

Is Review Logging Patterns safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Review Logging Patterns use?

Review Logging Patterns is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Review Logging Patterns use?

About 11k tokens (SKILL.md is roughly 42k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 9.4k tokens, read only when the agent opens those files.

What are the alternatives to Review Logging Patterns?

Skills that share tags, products or a category with Review Logging Patterns: Better Auth (einverne/dotfiles, 121 stars), Review Logging Patterns (activepieces/activepieces, 25k stars), Clerk (geekskai/blog, 103 stars) and Workflow Init (usenotra/notra, 256 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Review Logging Patterns?

evloghq (a GitHub organization) maintains it in evloghq/evlog, which has 1,887 GitHub stars. The repository holds 20 skills in this directory. The repository was last updated on October 7, 2026.

Source: evloghq/evlog on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.