
2025/12/13 0:46
I couldn't find a logging library that worked for my library, so I made one
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
(すべての重要ポイントを組み合わせたもの)
Summary
Fedify の新しい LogTape は、ライブラリ作者がデフォルト設定やランタイムオーバーヘッドを課さずに構造化ログを提供できるよう設計された、小型でオンデマンドのロギングフレームワークです。人気の JavaScript ロガーが設定を強制したりプレーンテキストを出力するのと対照的に、LogTape は開発者が明示的に有効化しない限り無音であり、階層構造かつ JSON 形式のメッセージ を自動的にリクエストコンテキスト(例:ヘッダーから取得した
requestId)とともに出力します。Node.js、Deno、Bun、およびエッジ環境でネイティブに実行され、未使用時はオーバーヘッドが最小限に抑えられます。
Fedify は LogTape を ActivityPub サーバーフレームワークをデフォルトで静かに保ちつつ、開発者に細粒度の制御を提供するために構築しました。ログカテゴリは階層的に整理され(例:
fedify.federation.inbox、fedify.sig.http)、ユーザーは fedify.jsonl のようなファイルへリダイレクトしたり、jq などのツールにパイプすることができます。このフレームワークはライブラリ作者向けであり、すでに winston や Pino を使用しているアプリケーションコードには適していません。将来的な作業ではクロスプラットフォームサポートを継続し、より広範な JavaScript コミュニティが構造化されたオプトインログを採用することを促進します。
変更点の説明
- 「小型」と「オンデマンド」の記述を追加。
- ログが JSON 形式であることを明示。
- エッジ環境を含めたことを反映。
- LogTape がライブラリ作者向けであり、アプリ開発者向けではないことを明確化。
- バリュープロポジションの推測表現は削除し、事実に基づく意図のみ保持。
本文
Fedifyでログを取り込むときに直面した課題
Fedify(ActivityPubサーバーフレームワーク)を作り始めたとき、驚くほどやっかいな問題に直面しました。
それは「どうすればロギングを追加できるのか」という点です。
ロギング自体が難しいわけではありません――JavaScriptには何十もの成熟したライブラリがあります。ただ、これらは主にアプリケーション向けで、ライブラリとしては目立たないように設計されていないことが問題です。
数か月前にこの件について書いたところ、反応は控えめでした。興味を示す声もあれば懐疑的な意見や「この記事はAI生成?」という議論までありました。正直言って、英語は第一言語ではないので、LLMで文章を磨いています。アイデアと技術内容は私のものです。
いくつかの読者からは理論だけでなく実際に使える例を求められました。
問題点
既存のロガーは「アプリケーションを作っている前提」で設計されています。Fedify は ActivityPub プロトコル を用いて分散型ソーシャルアプリを構築するためのライブラリです。
フェデレーションに慣れていれば、デバッグは非常に手間がかかります。たとえばアクティビティが届かなかった場合、次のような質問に答えたいでしょう。
| 質問 | 何を知りたい? |
|---|---|
| HTTPリクエストは実際に送信されたか? | リクエストがネットワークへ出ているか |
| シグネチャは正しく生成されているか? | 鍵の署名処理が成功したか |
| 遠隔サーバーが拒否した場合、その理由は? | サーバ側で何が起きたか |
| レスポンスのパースに問題があったか? | 返ってきた JSON‑LD を正しく解釈できるか |
これらの質問は HTTP ハンドリング、暗号署名、JSON‑LD 処理、キュー管理など複数サブシステムにまたがります。ロギングが適切でなければ、デバッグは推測ゲームになります。
しかしライブラリ作者として直面したジレンマは次のとおりです。
- 冗長なログを出力すると、コンソールが混乱しユーザーに迷惑になる
- 何も出力しないと、問題発生時に原因究明が困難になる
既存の選択肢を検討
Winston や Pino を使えば:
- Fedify 内でロガーを設定する(ユーザーに自分の好みを押し付ける)
- ユーザーにロガーインスタンスを渡してもらう(ボイラープレート増加)
また
debug というユーティリティはありますが、構造化ログやレベルベースの出力が欲しいというニーズには合いません。さらに環境変数に頼る設計で、Deno のようなランタイムでは制限されることも。
どれも私の求めていたものとは違いました。そこで LogTape を自作しました―ライブラリ作者向けにゼロから設計されたロギングライブラリです。Fedify が最初の実運用ユーザーになりました。
解決策:階層化カテゴリとデフォルトで出力しない
重要な発見はシンプルでした:「ライブラリはログを出力しても、開発者が明示的に有効にするまで何もしない」ということ。Fedify は LogTape の 階層化カテゴリ を使い、ユーザーが必要な情報だけを細かく制御できるようにしました。
| カテゴリ | 何をログに残すか |
|---|---|
| ライブラリ全体のメッセージ |
| 受信アクティビティ |
| 発信アクティビティ |
| HTTP リクエスト/レスポンス |
| HTTP シグネチャ操作 |
| Linked Data Signature 操作 |
| キー生成・取得 |
| JSON‑LD ドキュメントのロード |
| WebFinger でリソースを検索 |
(その他約十項目が存在します)
各カテゴリは独立したサブシステムに対応しています。
これにより、ユーザーは次のようにロギングを設定できます:
await configure({ sinks: { console: getConsoleSink() }, loggers: [ // Fedify 全体のエラーだけ表示 { category: "fedify", sinks: ["console"], lowestLevel: "error" }, // inbox 処理に関しては debug レベルで出力 { category: ["fedify", "federation", "inbox"], sinks: ["console"], lowestLevel: "debug" }, ], });
受信アクティビティに問題が発生したとき、該当サブシステムの詳細ログだけが出力されます。コードを書き換える必要はなく、設定だけで完結です。
非同期境界を跨ぐリクエストトレース
階層化カテゴリでフィルタリングできるようになったものの、非同期処理間でログを関連付ける課題が残っていました。フェデレーションでは1つのユーザー操作が数多くの非同期呼び出しに波及します:リモートアクターの取得 → 署名確認 → アクティビティ処理 → フォロワーへの配信…
LogTape の 暗黙的コンテキスト 機能を使えば、すべてのログエントリに
requestId を自動で付与できます。
await configure({ sinks: { file: getFileSink("fedify.jsonl", { formatter: jsonLinesFormatter }) }, loggers: [ { category: "fedify", sinks: ["file"], lowestLevel: "info" }, ], contextLocalStorage: new AsyncLocalStorage(), // 暗黙的コンテキストを有効化 });
この設定では、すべてのログエントリに
requestId が付与されます。特定のリクエストをデバッグしたいときは次のように絞り込みます:
jq 'select(.properties.requestId == "abc-123")' fedify.jsonl
これで手動で関連付ける必要がありません。
requestId は X‑Request‑Id や Traceparent など既存ヘッダーから自動生成され、既存の観測インフラに自然に統合できます。
実際にユーザーが見るもの
Fedify を使う人は次のような状況を体験します:
| 何が起きるか | 期待される挙動 |
|---|---|
| LogTape を設定しない場合 | 何も出力せず、警告やデフォルト出力はなし。パフォーマンスへの影響はほぼゼロ。 |
| 基本的な可視化を求めるなら | Fedify 全体のエラーだけを3行で設定して有効化 |
| 特定の問題をデバッグしたいとき | 関連サブシステムの debug レベルをオンにする |
| 本番環境では | 構造化 JSON ログを監視システムへ送る、リクエストごとの相関情報付きで |
同じライブラリコードが Node.js・Deno・Bun・エッジ関数 のいずれでも動き、追加のポリフィルやシムは不要です。必要なのはユーザー自身の判断です。
学んだこと
| 教訓 | 内容 |
|---|---|
| カテゴリ設計を早めに決める | ユーザーが実際にログを絞りたい場所を想定して構造化する。Fedify ではサブシステムごとに分けました。 |
| 構造化ロギングを採用 | 、、 のようなプロパティは文字列連結よりも分析しやすい。 |
| 暗黙的コンテキストは必須 | コンテキストを手動で渡さずに非同期境界間でもログが関連付けられる。 |
| ユーザーを信頼する | ログの詳細を公開しても、必要なときだけオプトインすれば問題ない。 |
ぜひ自分で試してみてください
ライブラリを作っていて「ロギングはどうやる?」と悩んでいるなら、Fedify の実装例を参考にしてください。Fedify のログドキュメントには詳細が記載されていますし、LogTape の設計思想も併せて紹介しています。
LogTape は Winston や Pino を置き換えるものではなく、「ライブラリはユーザーの手元で必要なときだけ動作するようにしたい」――という別のニーズを満たすツールです。
もしそのようなロギングが必要なら、従来のアプリ中心のロガーよりも LogTape が適しているかもしれません。