mcpField
C
create-evlog-framework-integration

create-evlog-framework-integration

github.com/evloghq開発1.9k531 回インストール2 日前に更新
リポジトリ

create-evlog-framework-integration について

evlogにフレームワーク連携を追加する貢献者向けのリポジトリSkillです。共有manifest方式とカスタムミドルウェアの使い分けを示し、必要なAPI、テスト、パッケージ、ドキュメントの作業を説明します。

主な機能

  1. 01manifest方式とカスタム連携方式
  2. 02共有ミドルウェアとストレージユーティリティ
  3. 03連携に必要なAPI
  4. 04フレームワーク別テスト指針
  5. 05ビルド、パッケージ、ドキュメントの確認項目

活用シーン

  1. 01フレームワークへのevlog対応追加
  2. 02連携アーキテクチャの選択
  3. 03連携テストとパッケージ変更の準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/evloghq/evlog --skill create-evlog-framework-integration

インストール後、Claude Code で/create-evlog-framework-integrationを入力して読み込まれたか確認します。

詳細

evlog フレームワーク統合の作成

evlog に新しいフレームワーク統合を追加します。推奨される方法は、リクエスト/レスポンス形式のミドルウェアを備えたフレームワークで、evlog/toolkit の defineFrameworkIntegration を使う マニフェストモード です。ライフサイクルが根本的に異なるフレームワークでは、低レベルの createMiddlewareLogger を使います。

2 つの方法

  • マニフェストモード(推奨、接続コードは約 30~80 行)。モジュールレベルで一度 defineFrameworkIntegration({ name, extractRequest, attachLogger, storage? }) を呼び出し、その後、integration.start(ctx, options) を呼び出して runWith の中でフレームワークの next() を実行する小さなミドルウェアを記述します。参考実装:packages/evlog/src/{hono,express,fastify,elysia,nestjs,orpc,react-router,sveltekit,workers}/index.ts はすべてこの方式を使用しています。
  • カスタムモード:フレームワークのライフサイクルが標準的なミドルウェアに適合しない場合は、createMiddlewareLogger を直接使います。現在のカスタムモード統合:Next.js(src/next/)、Nitro v2/v3(src/nitro/、src/nitro-v3/)、Eve(src/eve/)。

マニフェストモードは、従来型の HTTP フレームワークすべてに対応しています。ライフサイクルの開始時にリクエストを同期的に取得できない場合(サーバーアクション、モジュールレベルのフック、エージェントのターン)に限り、カスタムモードを使用してください。

必須 API サーフェス(AGENTS.md より)

すべてのフレームワーク統合で、次のものを公開する必要があります。

  1. evlog() ミドルウェア/プラグイン。完全な BaseEvlogOptions(drain、enrich、keep、include、exclude、routes、plugins)を受け付けます
  2. useLogger()(ALS ベース)。例外として認められているのは 2 つです。Workers(ALS を使うには互換フラグが必要であり、代わりに defineWorkerFetch がロガーをアタッチします)と Effect(ロガーは Effect コンテキストの EvlogLogger サービスです)
  3. log.fork() のサポート(マニフェストに storage を指定すると自動で有効になります)
  4. フレームワーク標準のアクセサー(c.get('log')、req.log、event.locals.log など)

PR タイトル

feat({framework}): add {Framework} middleware integration

スコープのタイミングに関する注意:セマンティック PR チェックはスコープ一覧を ベースブランチ から読み込むため、新しいスコープを追加する PR 自身は、そのスコープを検証できません。先行する小規模な PR でスコープを登録するか、追加する PR ではスコープを指定しないタイトル(feat: add {Framework} middleware integration)を使用してください。

対応箇所チェックリスト

#ファイル作業
1packages/evlog/src/{framework}/index.ts統合ソースを作成
2packages/evlog/tsdown.config.tsビルドエントリーと external を追加
3packages/evlog/package.jsonexports、typesVersions、オプションの peer dep、キーワードを追加
4packages/evlog/test/frameworks/{framework}.test.tsテストを作成(実際のリクエストドライバーと describeStandardHttpMatrix)
5packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snappnpm run build と pnpm test で再生成
6apps/docs/content/4.integrate/frameworks/{NN}.{framework}.mdフレームワークのドキュメントページを作成
7apps/docs/content/4.integrate/frameworks/00.overview.md表の行とカードを追加
8apps/docs/content/1.start/3.installation.md「Choose Your Framework」にカードを追加
9apps/docs/content/0.landing.mdフレームワークのコードスニペット枠を追加
10apps/docs/app/components/features/FeatureFrameworks.vueフレームワークのタブを追加
11skills/review-logging-patterns/SKILL.mdフレームワークのセットアップセクションを追加し、frontmatter の説明を更新
12packages/evlog/README.mdフレームワークのセクションと Framework Support 表の行を追加
13examples/{framework}/テスト用 UI を備えたサンプルアプリを作成(pnpm example {framework} で自動検出されるため、ルートスクリプトは不要)
14.changeset/{framework}-integration.mdchangeset を作成(minor)
15.github/workflows/semantic-pull-request.yml と .github/pull_request_template.md両ファイルで {framework} を PR スコープとして登録

重要:15 の対応箇所すべてに対処するまで、作業を完了したと見なさないでください。

命名規則

プレースホルダー例(Hono)用途
{framework}honoディレクトリ名、インポートパス、ファイル名、PR のスコープ
{Framework}Hono型名・インターフェース名における PascalCase

共有ユーティリティ

すべての統合は同じコアユーティリティを共有します。shared/ に存在するロジックを決して再実装しないでください。これらはコミュニティ製の統合向けに evlog/toolkit としても公開されています(カスタム統合のドキュメントを参照)。

ユーティリティ場所目的
defineFrameworkIntegration../shared/integrationマニフェストファクトリー: リクエストの抽出、ロガーの作成、アタッチ、ALS での実行
createMiddlewareLogger../shared/middleware低レベルなライフサイクル(カスタムモード): ロガーの作成、ルートのフィルタリング、テールサンプリング、emit、enrich、drain
BaseEvlogOptions../shared/middlewaredrain、enrich、keep、include、exclude、routes、plugins を備えた、ユーザー向けの基本オプション型
createLoggerStorage../shared/storage(evlog/toolkit/storage)AsyncLocalStorage に支えられた useLogger() 用に { storage, useLogger } を返すファクトリー。Workers / エッジでは evlog/toolkit/storage を推奨
shouldDeferEmitForResponse../shared/streamResponseストリーミングボディが閉じるまでワイドイベントを遅延させる(Hono/Elysia を参照)

defineFrameworkIntegration は自動的に以下を行います。

  • Web の Headers と Node の IncomingHttpHeaders の両方を正規化します(そのためヘッダー抽出器を選ぶ必要はありません)
  • 存在しない場合は requestId を生成します
  • createMiddlewareLogger を呼び出し、その { logger, finish, skipped, middlewareOptions } を返します
  • storage が指定された場合、log.fork() を自動的にアタッチします
  • 統合の ALS 内で下流のハンドラーを実行するための runWith(fn) を公開します
  • ランタイムが提供する場合に waitUntil を転送します(Workers、Workers 上の Hono)

ステップ 1: 統合のソース

packages/evlog/src/{framework}/index.ts を作成します。

テンプレート構造(マニフェストモード)
import type { AuditableLogger } from '../audit'
import { defineFrameworkIntegration } from '../shared/integration'
import type { BaseEvlogOptions } from '../shared/middleware'
import { createLoggerStorage } from '../shared/storage'

const { storage, useLogger } = createLoggerStorage(
  'middleware context. Make sure the evlog middleware is registered before your routes.',
  'evlog:{framework}',
)

export type Evlog{Framework}Options = BaseEvlogOptions
export { useLogger }

// Type augmentation for typed logger access (framework-specific):
// - Express: declare module 'express-serve-static-core' { interface Request { log: AuditableLogger } }
// - Hono:    export type EvlogVariables = { Variables: { log: AuditableLogger } }

const integration = defineFrameworkIntegration<{Framework}Context>({
  name: '{framework}',
  extractRequest: (ctx) => ({
    method: /* ctx.method */,
    path: /* ctx.path */,
    headers: /* Web Headers OR Node headers OR plain object */,
    requestId: /* x-request-id header or undefined → auto-generated */,
  }),
  attachLogger: (ctx, logger) => {
    // Store in framework-idiomatic location:
    // - Hono:    c.set('log', logger)
    // - Express: req.log = logger
  },
  storage,
})

export function evlog(options: Evlog{Framework}Options = {}): FrameworkMiddleware {
  return async (ctx, next) => {
    const { skipped, finish, runWith } = integration.start(ctx, options)
    if (skipped) {
      await next()
      return
    }
    try {
      await runWith(() => next())
      await finish({ status: /* extract status from ctx */ })
    } catch (error) {
      await finish({ error: error as Error })
      throw error
    }
  }
}
リファレンス実装
  • Hono: src/hono/index.ts。c.set('log', logger) + ALS の useLogger()、shouldDeferEmitForResponse によるストリーミングの遅延、waitUntil の検出
  • Express: src/express/index.ts。req.log、ALS ストレージ、終了ステータス用の res.on('finish')
  • Fastify: src/fastify/index.ts。Fastify フック(onRequest / onResponse / onError)、fastify-plugin ラッパー
  • Elysia: src/elysia/index.ts。.derive({ as: 'global' }) を備えたプラグイン、storage.enterWith スタイルの ALS、ストリーミングの遅延
  • NestJS: src/nestjs/index.ts。マニフェストの上に構築された EvlogModule.forRoot() / forRootAsync()。@nestjs/platform-fastify ではミドルウェアが生のリクエストしか参照できないため、preHandler フック(HttpAdapterHost 経由で登録)がロガーを FastifyRequest.log にコピーします
  • oRPC: src/orpc/index.ts。evlog() プロシージャミドルウェア + withEvlog(handler) ラッパー
  • React Router: src/react-router/index.ts。loggerContext = createContext<AuditableLogger>()
  • SvelteKit: src/sveltekit/index.ts。evlog() ハンドル + evlogHandleError() + createEvlogHooks()
  • Workers: src/workers/index.ts。defineWorkerFetch / withEvlog、ALS の useLogger() なし(compat-flag の制約)

enterWith() でバインドする統合では、src/shared/asyncStorageScope.ts の createSharedEnterWithStorage を使用してください。そのケイパビリティプローブは、異なるストアを持つ一時的なスコープで実行され、Bun および Node でインポーターの非同期コンテキストを保持します。

アーキテクチャの主要ルール
  1. defineFrameworkIntegration を優先する: これはヘッダーの正規化、リクエスト ID の生成、ALS、フォークのアタッチ、および waitUntil を処理します。
  2. ステータス / エラー報告はフレームワーク側にとどめる: 成功時は finish({ status }) を呼び出し、失敗時は finish({ error }) を呼び出します。finish は emit + enrich + drain + プラグインフックを実行します。
  3. エラーを再スローする: finish({ error }) の後に再スローして、フレームワーク自身のエラーハンドラーが引き続き実行されるようにします。
  4. ストリーミングレスポンス: フレームワークがストリーミングボディを返せる場合、ストリームが閉じるまで emit を遅延させます(shouldDeferEmitForResponse。Hono と Elysia を参照)。
  5. フレームワーク SDK はオプションのピア依存関係: 決してバンドルしないでください。
  6. パイプラインロジックを重複させない: runEnrichAndDrain は createMiddlewareLogger/finish の内部専用です。
  7. 型付きコンテキストアクセス用の型ヘルパーをエクスポートする(例: Hono 用の EvlogVariables)。
カスタムモードにフォールバックすべき場合

次の場合は createMiddlewareLogger を直接使用します(defineFrameworkIntegration をスキップします):

  • ミドルウェアに明確な「リクエスト入口 / レスポンス出口」のペアがない場合(Next.js App Router のサーバーアクション、Eve エージェントのターン)。
  • ロガーの生成が、モジュールシステムが所有する複数のライフサイクルフェーズにまたがる場合(Nitro プラグイン + フック)。
  • レスポンスストリームが完了するまでステータスが判明せず、フレームワークがそのためのフックを提供していない場合。

ステップ 2: ビルド設定

packages/evlog/tsdown.config.ts にビルドエントリを追加します:

'{framework}/index': 'src/{framework}/index.ts',

また、フレームワーク SDK を external 配列に追加します(例: 'elysia'、'fastify')。

ステップ 3: パッケージエクスポート

packages/evlog/package.json 内:

exports 内(最後のフレームワークエントリの後):

"./{framework}": {
  "types": "./dist/{framework}/index.d.mts",
  "import": "./dist/{framework}/index.mjs",
  "default": "./dist/{framework}/index.mjs"
}

typesVersions["*"] 内: "{framework}": ["./dist/{framework}/index.d.mts"]

peerDependencies 内(バージョン範囲)+ peerDependenciesMeta("optional": true)、そしてフレームワーク名を keywords に追加します。

test/toolkit/entrypoint-registry.test.ts は、エクスポートに対応する typesVersions["*"] または tsdown.config.ts のエントリがない場合、あるいはその import と default が types の隣にある .mjs を指していない場合に失敗します。これはビルドなしで実行されます。

ステップ 4: テスト

packages/evlog/test/frameworks/{framework}.test.ts を作成します。まず packages/evlog/test/README.md を読んでください。特に Framework runtime fidelity の表を確認してください。

2 つの絶対条件:

  1. 実際のリクエストドライバー。 フレームワーク自身のドライバーを使用します: supertest(Express/NestJS)、app.request()(Hono)、app.inject()(Fastify)、app.handle(new Request(...))(Elysia)。Node に適したドライバーが存在しない場合は、現実的な入力形状でユーザー向けのコントラクトを直接呼び出します(SvelteKit と React Router のテストを参照)。代替をテストするために内部を抽出しないでください。
  2. 共有マトリックスを接続する。 test/helpers/frameworkMatrix.ts から describeStandardHttpMatrix({ name, mount }) を呼び出します。これはすべての HTTP フレームワークについて標準スイープ(イベントの発行、x-request-id、ルートサービス)をカバーします。

マトリックスに加えて、フレームワーク固有のサーフェスをカバーします:

  1. フレームワークネイティブのアクセサーがロガーを返す(c.get('log')、req.log など)
  2. エラーハンドリング。エラーがキャプチャされ、イベントにエラーレベル + 詳細が含まれ、エラーが再スローされる
  3. ルートフィルタリング。スキップされたルートはロガーを生成せず、drain/enrich をスキップする
  4. コンテキストの蓄積。logger.set() のデータが発行されたイベントに現れる
  5. drain / enrich / keep コールバック(test/helpers/framework.ts の createPipelineSpies()、assertHttpEventEmitted、waitForDrainCalls、findEventViaDrain を使用)
  6. drain/enrich のエラー耐性。そこでのエラーがリクエストを壊すことは決してない
  7. useLogger(): ネイティブアクセサーと同じロガーで、非同期境界を越えて機能し、コンテキスト外ではスローする。ALS のない統合ではこれをスキップし、代わりにそれが提供するアクセサーをテストします: Workers ではそれはハンドラーの第 4 引数で、defineWorkerFetch / withEvlog から渡されます
  8. ストリーミング(該当する場合)。ボディが閉じるまでイベントが遅延される

時間ベースのものにはフェイクタイマーを使用し、! の代わりに defined() を使用します。

ステップ 5: フレームワークドキュメントページ

包括的で自己完結したガイドを apps/docs/content/4.integrate/frameworks/{NN}.{framework}.md として作成します。既存のファイルを確認して、次に使用するゼロ埋めされた番号を調べてください。

フロントマター:

---
title: {Framework}
description: Using evlog with {Framework}: automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in {Framework} applications.
navigation:
  title: {Framework}
  icon: i-simple-icons-{framework}
links:
  - label: Source Code
    icon: i-simple-icons-github
    to: https://github.com/evloghq/evlog/tree/main/examples/{framework}
    color: neutral
    variant: subtle
---

セクション (参考として Express/Hono/Elysia のページに従ってください):

  1. クイックスタート: インストール + ミドルウェアの登録 (コピー&ペーストできる最小限のセットアップ)
  2. ワイドイベント: 段階的な log.set() の使用法
  3. useLogger(): リクエストを渡さずにサービスからロガーにアクセスする方法、または、ALS を使用しない統合の場合には、その代わりに同梱されているアクセサー
  4. エラーハンドリング: createError() + parseError() + フレームワークのエラーハンドラー
  5. Drain とエンリッチャー: インライン例を伴うミドルウェアオプション
  6. パイプライン (バッチ処理とリトライ): createDrainPipeline の例
  7. テールサンプリング: keep コールバック
  8. ルートフィルタリング: include / exclude / routes
  9. クライアントサイドロギング: HTTP drain (evlog/http) (フレームワークにクライアントサイドの対応がある場合のみ)
  10. ローカルで実行: クローン + pnpm example {framework}
  11. カードグループ: GitHub ソースへのリンク

ステップ 6: 概要とインストールのカード

apps/docs/content/4.integrate/frameworks/00.overview.md 内で:

  1. 概要テーブルに行を追加: フレームワーク名、インポート、型、ロガーへのアクセス、ステータス
  2. 適切なセクションにカードを追加し、ブートストラップ/アクセスのパターンが新しい場合は API チートシートにも行を追加

apps/docs/content/1.start/3.installation.md 内で: 「フレームワークを選択」の ::card-group に、フレームワーク概要と同じ順序でカードを追加します。

ステップ 7: ランディングページ

apps/docs/content/0.landing.md 内で、FeatureFrameworks の MDC セクション (スロット #nuxt、#nextjs、#hono、…) を見つけ、新しいスロットを追加します:

  #{framework}
  ```ts [src/index.ts]
  // Framework-specific code example showing evlog usage

## Step 8: FeatureFrameworks Component

Update `apps/docs/app/components/features/FeatureFrameworks.vue`:

1. Add the framework to the `frameworks` array with its icon and the next available `tab` index
2. Add a `<div v-if="activeTab === {N}">` with `<slot name="{framework}" />` in the template

Icons use Simple Icons format: `i-simple-icons-{name}`.

## Step 9: Update the Public Skill

In `skills/review-logging-patterns/SKILL.md` (published on evlog.dev):

1. Add `### {Framework}` in the **"Framework Setup"** section, in the same order as the docs
2. Include: import + `initLogger` + middleware setup; native logger access; a `useLogger()` snippet, or the accessor that replaces it when the integration has no ALS; full pipeline example (`drain`, `enrich`, `keep`)
3. Update the `description:` line in the YAML frontmatter to mention the new framework name

## Step 10: Update README

In `packages/evlog/README.md` (root `README.md` is a symlink):

1. Add a `## {Framework}` section near the other framework sections with a minimal setup snippet and a link to the example app
2. Add a row to the **Framework Support** table

Keep the snippet short: init, register middleware, one route handler showing logger access.

## Step 11: Example App

Create `examples/{framework}/` with a runnable app demonstrating all evlog features. It is auto-discovered by the root runner: `pnpm example {framework}` (which loads the root `.env` via dotenv, so no root `package.json` change is needed).

The app must include:

1. **`evlog()` middleware** with `drain` (PostHog) and `enrich` callbacks
2. **Health route**: basic `log.set()` usage
3. **Data route**: context accumulation with user/business data, using `useLogger()` in a service function, or the integration's own accessor when it has no ALS
4. **Error route**: `createError()` with status/why/fix/link
5. **Error handler**: framework's error handler with `parseError()` + manual `log.error()`
6. **Test UI**: served at `/`, a self-contained HTML page with buttons to hit each route and display JSON responses

**Drain must use PostHog** (`createPostHogDrain()` from `evlog/posthog`). `POSTHOG_API_KEY` is set in the root `.env` (maintainer's key, not committed), so every example exercises a real external drain. Without the env var the drain resolves to `null` and skips, so someone cloning the repo sends nothing anywhere unless they opt in with their own key. Enable pretty printing for readable local output.

**Type the `enrich` callback parameter explicitly**, as `(ctx: EnrichContext) => ...` with `type EnrichContext` imported from `evlog`.

### Test UI

Reference: `examples/hono/src/ui.ts`, a single `src/ui.ts` exporting `testUI()` returning a self-contained dark-theme HTML string (route list with method badges, click-to-fetch, JSON display, status colors, response time). Register the `/` route **before** the evlog middleware so it isn't logged.

### Required files

| File | Purpose |
|------|---------|
| `src/index.ts` | App with all features demonstrated |
| `src/ui.ts` | Test UI |
| `package.json` | `dev` and `start` scripts (`bun --watch src/index.ts` / `bun src/index.ts`) |
| `tsconfig.json` | TypeScript config (if needed) |
| `README.md` | How to run + link to the UI |

There is also `examples/community-framework-skeleton/` showing the community-facing (toolkit-only) variant, so keep it in mind if the new integration changes the toolkit contract.

## Step 12: Changeset

Create `.changeset/{framework}-integration.md`:

```markdown
---
"evlog": minor
---

feat({framework}): add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support

ステップ 13: PR スコープ

.github/workflows/semantic-pull-request.yml の scopes リストに、および .github/pull_request_template.md の Scopes セクションに、アルファベット順で {framework} を追加します。PR タイトルのセクションにあるタイミングに関する注意点を忘れないでください。この登録は、その登録をすでに含むベースブランチを持つ PR に対してのみ有効になります。

検証

クリーンインストール後、まずリポジトリのルートから pnpm run dev:prepare を実行し、その後で次を実行します:

pnpm run dev:prepare
cd packages/evlog
pnpm run build    # required before test — api-surface snapshot is gated on dist/
pnpm run test
pnpm run lint

次に、サンプルの型チェックを行います:

cd examples/{framework}
pnpm exec tsc --noEmit

コメント