review-logging-patterns
コード内のロギングパターンをレビューし、evlog の導入を提案するスキル。Nuxt、Next.js、SvelteKit、Nitro、TanStack Start、React Router、NestJS、Express、Hono、Fastify、Elysia、Cloudflare Workers、スタンドアロン TypeScript へのセットアップをガイドし、`console.log` の乱用・非構造化エラー・コンテキスト欠落を検出する。構造化イベント・エラー管理・ドレインアダプター(Axiom、OTLP、HyperDX、PostHog、Sentry、Better Stack、Datadog)・サンプリング・エンリッチャー・AI SDK 連携(トークン使用量、ツール呼び出し、ストリーミング指標、テレメトリ統合、コスト推定、埋め込みメタデータ)まで幅広くカバーする。
description の原文を見る
Review code for logging patterns and suggest evlog adoption. Guides setup on Nuxt, Next.js, SvelteKit, Nitro, TanStack Start, React Router, NestJS, Express, Hono, Fastify, Elysia, Cloudflare Workers, 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), sampling, enrichers, and AI SDK integration (token usage, tool calls, streaming metrics, telemetry integration, cost estimation, embedding metadata).
SKILL.md 本文
ログパターンのレビュー
TypeScript/JavaScriptコードベースのログパターンをレビューし改善します。散在したconsole.logsを構造化された広範なイベントに変換し、汎用エラーを自己記述的な構造化エラーに変換します。
使用時機
- 新しいプロジェクトまたは既存プロジェクトでevlogをセットアップする(サポート対象のフレームワーク)
- ログベストプラクティスについてコードをレビューする
- console.log文を構造化ログに変換する
- より多くのコンテキストを持つエラー処理を改善する
- ログドレイン、サンプリング、エンリッチメントを設定する
クイックリファレンス
| 対象 | リソース |
|---|---|
| 広範なイベントパターン | references/wide-events.md |
| エラー処理 | references/structured-errors.md |
| コードレビューチェックリスト | references/code-review.md |
| ドレインパイプライン | references/drain-pipeline.md |
インストール
npm install evlog
フレームワークセットアップ
Nuxt
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: { service: 'my-app' },
include: ['/api/**'],
},
})
すべてのevlog関数(useLogger、createError、parseError、log)は自動インポートされます — importステートメント不要です。
// server/api/checkout.post.ts — インポート不要
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: user.id, plan: user.plan } })
return { success: true }
})
ドレイン、エンリッチメント、テールサンプリングはサーバープラグイン内のNitroフックを使用します:
// server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
クライアントトランスポート(自動設定Vueプラグイン):
// nuxt.config.ts
evlog: {
transport: { enabled: true }, // ログが /api/_evlog/ingest に送信される
}
クライアント側:log、setIdentity、clearIdentityはコンポーネントで自動インポートされます。
Next.js
ステップ1:中央設定を作成 — すべてのエクスポートはここから来ます:
// 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,
})
ステップ2:ルートハンドラーをwithEvlog()でラップします:
// app/api/checkout/route.ts
import { withEvlog, useLogger } from '@/lib/evlog'
export const POST = withEvlog(async (request: Request) => {
const log = useLogger() // 引数なし — AsyncLocalStorageを使用
log.set({ user: { id: 'user_123', plan: 'enterprise' } })
log.set({ cart: { items: 3, total: 14999 } })
return Response.json({ success: true })
})
ステップ3:Server Actions — 同じwithEvlog()ラッパー:
// 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 }
})
ステップ4:ミドルウェア(オプション — x-request-id + タイミングヘッダーを設定):
// proxy.ts
import { evlogMiddleware } from 'evlog/next'
export const proxy = evlogMiddleware()
export const config = { matcher: ['/api/:path*'] }
ステップ5:クライアントプロバイダー — ルートレイアウトをラップ:
// 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>
)
}
ステップ6:クライアントログ — あらゆるクライアントコンポーネント内:
'use client'
import { log, setIdentity, clearIdentity } from 'evlog/next/client'
setIdentity({ userId: 'usr_123' })
log.info({ action: 'checkout_click' })
clearIdentity()
ステップ7(オプション):インストルメンテーション — スタートアップ + グローバルonRequestError(SSR/RSCエラー(withEvlog外))。ルートinstrumentation.tsでdefineNodeInstrumentation(() => import('./lib/evlog'))を使用してNodeをゲートし、インポートをキャッシュするか、register/onRequestErrorを手動で記述します — どちらも有効です。カスタムロジックの場合、evlogのregister/onRequestErrorをlib/evlog.ts内でラップ(独自の初期化またはメトリクスで作成)し、再エクスポートします。
lib/evlog.tsからcreateInstrumentation()をcreateEvlog()と共にエクスポートします。lockLoggerとの共存についてはフレームワークドキュメントを参照してください。
ステップ8:クライアントインジェストエンドポイント — クライアントログを受け取ります:
// 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
// src/hooks.server.ts
import { initLogger } from 'evlog'
import { createEvlogHooks } from 'evlog/sveltekit'
initLogger({ env: { service: 'my-app' } })
export const { handle, handleError } = createEvlogHooks()
ルートハンドラーでevent.locals.log経由、またはコールスタック内の任意の場所からuseLogger()でロガーにアクセスします:
// 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' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
// 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はNitro v3を使用します。evlogをインストールして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' } })],
})
__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)],
},
})
nitro/contextからuseRequest()を使用してロガーにアクセス:
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' } })],
})
ルートでevlog/nitroからuseLoggerをインポートします。
NestJS
// src/app.module.ts
import { Module } from '@nestjs/common'
import { EvlogModule } from 'evlog/nestjs'
@Module({
imports: [EvlogModule.forRoot()],
})
export class AppModule {}
EvlogModule.forRoot()はグローバルミドルウェアを登録します。useLogger()を使用して、あらゆるコントローラーまたはサービスからリクエストスコープのロガーにアクセスします:
import { useLogger } from 'evlog/nestjs'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
},
})
NestJS DIを使用した非同期設定の場合、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: [] })
})
useLogger()を使用して、reqを渡すことなくコールスタック内の任意の場所からロガーにアクセスします:
import { useLogger } from 'evlog/express'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
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: [] })
})
ハンドラー内でc.get('log')経由でロガーにアクセスします。useLogger()なし — c.get('log')を使用して明示的に渡すか、非同期境界を越えてuseLogger()が必要な場合はExpress/Fastify/Elysiаを使用します。
構造化エラー:createError()をスロー、次にapp.onErrorでparseError()を使用してparsed.status as ContentfulStatusCodeをc.json()に渡します(Honoは状態引数をContentfulStatusCodeとして型付けし、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,
)
})
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
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はevlog広範なイベントロガーです(リクエスト上のFastifyの組み込みpinoロガーをシャドウイング)。Fastifyのpinoロガーはfastify.log経由でアクセス可能です。
useLogger()を使用して、requestを渡すことなくコールスタック内の任意の場所からロガーにアクセスします:
import { useLogger } from 'evlog/fastify'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
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)
useLogger()を使用して、コールスタック内の任意の場所からロガーにアクセスします:
import { useLogger } from 'evlog/elysia'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
// 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(),
]
ローダーやアクションでcontext.get(loggerContext)経由でロガーにアクセス:
// 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: [] }
}
useLogger()を使用して、コンテキストを渡すことなくコールスタック内の任意の場所からロガーにアクセスします:
import { useLogger } from 'evlog/react-router'
async function findUsers() {
const log = useLogger()
log.set({ db: { query: 'SELECT * FROM users' } })
}
ドレイン、エンリッチメント、テールサンプリングを備えたフルパイプライン:
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
},
}),
]
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プラグイン(あらゆるViteベースのフレームワーク)
あらゆるViteベースのプロジェクト(SvelteKit、Astro、SolidStart、React+Viteなど)では、自動初期化、自動インポート、ビルド時機能のためにViteプラグインを使用します:
// vite.config.ts
import evlog from 'evlog/vite'
export default defineConfig({
plugins: [
evlog({
service: 'my-app',
autoImports: true, // log、createEvlogError、parseErrorを自動インポート
strip: ['debug'], // 本番環境でlog.debug()を削除
sourceLocation: true, // 開発+本番環境でファイル:行を注入
client: { // クライアント側ログ
transport: { endpoint: '/api/logs' },
},
}),
],
})
サーバー側ミドルウェア(ドレイン、エンリッチメント、キープ、ルート)は引き続きフレームワークインテグレーション(例:Hono/Express/SvelteKitのevlog()ミドルウェア)で設定されます。Viteプラグインはビルド時DXのみを処理します。
スタンドアロン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() // スタンドアロンではマニュアルエミットが必須
設定オプション
すべてのオプションはNuxt(evlogキー)、Nitro(evlog()に渡す)、Next.js(createEvlog())、スタンドアロン(initLogger())で機能します。
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
env.service / service | string | 'app' | ログ内のサービス名 |
enabled | boolean | true | グローバルトグル(false時はnoop) |
pretty | boolean | 開発時true | プリティツリー形式 vs JSON |
silent | boolean | false | コンソール出力を抑制。イベントはドレインに送信 |
include | string[] | すべてのルート | ログするルートグロブパターン |
exclude | string[] | なし | 除外するルートパターン(優先) |
routes | Record<string, { service }> | -- | ルート固有のサービス名 |
minLevel | 'debug' | 'info' | 'warn' | 'error' | 'debug' | グローバルlog APIとクライアントlogのハードしきい値(リクエスト広範なイベントではない)。リクエストの確率的ボリュームにsampling.ratesを使用 |
sampling.rates | object | -- | ヘッドサンプリング:{ info: 10, warn: 50 } (0-100%) |
sampling.keep | array | -- | テールサンプリング:[{ status: 400 }, { duration: 1000 }] |
drain | (ctx) => void | -- | ドレインコールバック(Next.js、スタンドアロン) |
enrich | (ctx) => void | -- | エンリッチコールバック(Next.js) |
keep | (ctx) => void | -- | カスタムテールサンプリングコールバック(Next.js) |
redact | boolean | RedactConfig | 本番環境でtrue | 本番環境でデフォルト有効。falseで無効化。オブジェクトで細粒度制御 |
Nitroフック(Nuxt、Nitro v2/v3)
| フック | タイミング | 用途 |
|---|---|---|
evlog:drain | エンリッチメント後 | 外部サービスにイベント送信 |
evlog:enrich | エミット後、ドレイン前 | 派生コンテキスト追加 |
evlog:emit:keep | エミット中 | カスタムテールサンプリングロジック |
close | サーバーシャットダウン | ドレインパイプラインバッファをフラッシュ |
ドレインアダプター
| アダプター | インポート | 環境変数 |
|---|---|---|
| Axiom | evlog/axiom | AXIOM_TOKEN、AXIOM_DATASET |
| OTLP | evlog/otlp | OTLP_ENDPOINT (or OTEL_EXPORTER_OTLP_ENDPOINT) |
| HyperDX | evlog/hyperdx | HYPERDX_API_KEY (オプション HYPERDX_OTLP_ENDPOINT; デフォルト 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 |
| Datadog | evlog/datadog | DD_API_KEY or DATADOG_API_KEY、オプション DD_SITE / DATADOG_LOGS_URL |
| ファイルシステム | evlog/fs | なし(ローカルファイルシステム) |
| HTTP(ブラウザインジェスト) | evlog/http | なし(コード内でendpointを設定)。evlog/browserは非推奨; 同じAPI、次のメジャーで削除 |
Nuxt/NitroではNUXT_プレフィックス(例:NUXT_AXIOM_TOKEN)を使用して、値がuseRuntimeConfig()経由で利用可能になるようにします。すべてのアダプターもプレフィックスなし変数をフォールバックとして読み込みます。
フレームワークごとの設定パターン:
// 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: ミドルウェアオプションでドレイン渡し
import { createAxiomDrain } from 'evlog/axiom'
app.use(evlog({ drain: createAxiomDrain() }))
// Fastify: プラグインオプションでドレイン渡し
import { createAxiomDrain } from 'evlog/axiom'
await app.register(evlog, { drain: createAxiomDrain() })
// NestJS: モジュールオプションでドレイン渡し
import { createAxiomDrain } from 'evlog/axiom'
EvlogModule.forRoot({ drain: createAxiomDrain() })
// Next.js: 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 })
// スタンドアロン: initLogger()にドレイン渡し
initLogger({ env: { service: 'my-app' }, drain: createAxiomDrain() })
references/drain-pipeline.mdをバッチ処理、リトライ、バッファオーバーフロー設定に参照してください。
エンリッチャー
組み込み:createUserAgentEnricher()、createGeoEnricher()、createRequestSizeEnricher()、createTraceContextEnricher() — すべて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: lib/evlog.ts内
createEvlog({
enrich: (ctx) => {
for (const enricher of enrichers) enricher(ctx)
ctx.event.region = process.env.VERCEL_REGION
},
})
自動マスキング(PII保護)
組み込みマスキングは広範なイベントから機密データをスクラブしてからコンソール出力と、ドレインがデータを見る前に削除します。本番環境でデフォルト有効(NODE_ENV === 'production')、開発環境で無効。スマート部分マスキングを使用 — デバッグに十分なコンテキストを保持。
// 本番環境で無効化(オプトアウト)
evlog: { redact: false }
// 組み込みの上にカスタムパスを追加
evlog: {
redact: {
paths: ['user.password', 'headers.authorization'],
}
}
// 特定の組み込みのみ
evlog: {
redact: {
builtins: ['email', 'creditCard'],
}
}
// 組み込みなし、カスタムのみ(フラット[REDACTED]置換を使用)
evlog: {
redact: {
builtins: false,
paths: ['user.ssn'],
patterns: [/SECRET_\w+/g],
}
}
スマートマスキング出力を備えた組み込みパターン:
| パターン | 入力例 | マスク出力 |
|---|---|---|
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 |
すべてのフレームワークで機能:Nuxt(evlog設定)、Nitro(evlog()モジュールオプション)、Next.js(createEvlog())、スタンドアロン(initLogger())、および全ミドルウェアインテグレーション(Hono、Express、Fastify、Elysia、NestJS)。
AI SDKインテグレーション
Vercel AI SDKからトークン使用量、ツール呼び出し、モデル情報、ストリーミングメトリクス、ツール実行タイミング、コスト推定、埋め込みメタデータを広範なイベント内にキャプチャします。evlog/aiからインポート。ai >= 6.0.0をピア依存として要求。
基本セットアップ(ミドルウェア)
import { createAILogger } from 'evlog/ai'
const log = useLogger(event) // または任意のRequestLogger
const ai = createAILogger(log)
const result = streamText({
model: ai.wrap('anthropic/claude-sonnet-4.6'), // 文字列またはモデルオブジェクトを受け入れる
messages,
})
ai.wrap()はモデルミドルウェアを使用してすべてのLLM呼び出しを透過的にキャプチャします。generateText、streamText、ToolLoopAgentで機能。
テレメトリインテグレーション(深層的な可視性)
ツール実行タイミング、成功/失敗追跡、および総生成ウォール時間については、createEvlogIntegration()を追加:
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),
experimental_telemetry: {
isEnabled: true,
integrations: [createEvlogIntegration(ai)],
},
})
これによりai.tools(ツールごとの{ name, durationMs, success, error? })とai.totalDurationMsが広範なイベント内に追加されます。
埋め込み
const { embedding, usage } = await embed({ model: embeddingModel, value: query })
ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })
embedManyの場合、バッチカウントを渡す:
ai.captureEmbed({ usage, model: 'text-embedding-3-small', count: documents.length })
コスト推定
広範なイベント内でai.estimatedCostを取得するため価格マップを渡す:
const ai = createAILogger(log, {
cost: {
'claude-sonnet-4.6': { input: 3, output: 15 },
'gpt-4o': { input: 2.5, output: 10 },
},
})
広範なイベントaiフィールド
含まれるもの:calls、model、provider、inputTokens、outputTokens、totalTokens、cacheReadTokens、reasoningTokens、finishReason、toolCalls、steps、msToFirstChunk、msToFinish、tokensPerSecond、error、tools(テレメトリインテグレーション経由)、totalDurationMs(テレメトリインテグレーション経由)、embedding(captureEmbed経由)、estimatedCost(costオプション経由)。
検出すべきアンチパターン:
| アンチパターン | 修正 |
|---|---|
onFinishのマニュアルトークン追跡 | ai.wrap() — ミドルウェアが自動キャプチャ |
console.log('tokens:', result.usage) | ai.wrap() — 広範なイベント内の構造化ai.*フィールド |
| AI可視化なし | createAILogger(log) + ai.wrap()追加 |
| ツール実行タイミングなし | experimental_telemetry.integrationsにcreateEvlogIntegration(ai)追加 |
| マニュアルコスト計算 | createAILogger()のcostオプション使用 |
構造化エラー
import { createError } from 'evlog' // または Nuxt で自動インポート
// 最小限
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',
cause: originalError,
})
// バックエンド専用コンテキスト(広範なイベント / ドレイン — HTTPボディまたはparseError()には決して含まれない)
throw createError({
message: 'Not allowed',
status: 403,
why: 'Insufficient permissions',
internal: { correlationId: 'req_abc', resourceId: 'proj_123' },
})
フロントエンド — parseError()でユーザー向けフィールドを抽出(internalはクライアントに決して返されない):
import { parseError } from 'evlog'
const error = parseError(err)
// error.message、error.status、error.why、error.fix、error.link
references/structured-errors.mdを一般的なパターンとテンプレートに参照してください。
検出すべきアンチパターン
| アンチパターン | 修正 |
|---|---|
1つの関数内の複数console.log | log.set()を備えた単一広範なイベント |
throw new Error('...') | throw createError({ message, status, why, fix }) |
console.error(e); throw e | log.error(e); throw createError(...) |
| リクエストハンドラーでログなし | useLogger(event) / useLogger() / createRequestLogger()追加 |
フラットログデータ{ uid, n, t } | グループ化オブジェクト:{ user: {...}, cart: {...} } |
機密データログlog.set({ user: body }) | 明示的フィールド:{ user: { id: body.id, plan: body.plan } } + redact: true有効化 |
why / message内のサポート専用ID | createError({ ..., internal: { ... } })を使用(ユーザー向けでない診断) |
references/code-review.mdを完全なチェックリストに参照してください。
リファレンスファイルのロード
作業対象に基づいてロード — 一度にすべてロードしないでください:
- 広範なイベント設計 →
references/wide-events.md - エラー改善 →
references/structured-errors.md - 完全コードレビュー →
references/code-review.md - ドレインパイプラインセットアップ →
references/drain-pipeline.md
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- hugorcd
- リポジトリ
- hugorcd/evlog
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/hugorcd/evlog / ライセンス: MIT
関連スキル
agent-browser
AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。
anyskill
AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。
engram
AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。
skyvern
AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。
pinchbench
PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。
openui
OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。