JavaScriptライブラリを構築していて、ロギングが必要なら、LogTapeがきっと気に入るでしょう

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

JavaScriptライブラリの構築は繊細なバランスが求められます。有用な機能を提供しながら、ユーザーの選択や制約を尊重する必要があります。ロギング—多くのライブラリがデバッグ、モニタリング、ユーザーサポートのために必要とするもの—に関しては、このバランスが特に難しくなります。

JavaScriptエコシステムでは、この課題に対してさまざまなアプローチが進化してきましたが、それぞれに独自のトレードオフがあります。LogTapeは異なる道を提供します。これはライブラリ作者を特に念頭に置いて設計されています。

ライブラリロギングの現状

これまでにライブラリを構築したことがあれば、ロギングのジレンマに遭遇したことがあるでしょう。あなたのライブラリはロギングから恩恵を受けるでしょう—おそらく、ユーザーが統合の問題をデバッグしたり、内部状態の変化を追跡したり、パフォーマンスのボトルネックに関する洞察を提供したりするのに役立ちます。しかし、この機能を責任を持って追加するにはどうすればよいでしょうか?

現在、人気のあるライブラリはこの課題をいくつかの方法で処理しています:

debugアプローチ
ExpressやSocket.IOなどのライブラリは、軽量なdebugパッケージを使用しており、ユーザーは環境変数(DEBUG=express:*)を通じてロギングを有効にできます。これはうまく機能しますが、ユーザーの既存のロギングインフラストラクチャと統合されない別のロギングシステムを作成します。
カスタムロギングシステム
MongooseやPrismaなどのライブラリは、独自のロギングメカニズムを構築しています。Mongooseはmongoose.set('debug', true)を提供し、Prismaは独自のロギング設定を使用しています。これらのアプローチは機能しますが、各ライブラリがユーザーが別々に学ぶ必要がある独自のロギングAPIを作成します。
アプリケーション重視のライブラリ
winstonPinoBunyanは強力なロギングソリューションですが、主にライブラリではなくアプリケーション向けに設計されています。これらをライブラリで使用すると、重要な依存関係を課し、ユーザーの既存のロギング選択と潜在的に競合する可能性があります。
ロギングなし
多くのライブラリ作者は複雑さを完全に避け、ライブラリを無音のままにして、関係者全員のデバッグをより困難にしています。
依存性注入
一部のライブラリは、設定やコンストラクタパラメータを通じてアプリケーションからロガーインスタンスを受け入れるという、より洗練されたアプローチを採用しています。これにより関心事の明確な分離が維持され、ライブラリはアプリケーションが選択したどのロギングシステムでも使用できます。ただし、このパターンはより複雑なAPIを必要とし、ロギング依存関係を理解して設定するための追加の負担をライブラリユーザーに課します。

各アプローチは本物の問題に対する合理的な解決策を表していますが、どれも中核的な緊張関係を完全に解決していません:ユーザーに選択を強制することなく、価値ある診断機能を提供するにはどうすればよいでしょうか?

断片化問題

ライブラリがそれぞれ独自の方法でロギングを解決するとき、もう一つの課題が浮上します:断片化です。ウェブフレームワークにExpress、リアルタイム通信にSocket.IO、HTTPリクエストにAxios、データベースアクセスにMongoose、その他いくつかの専門ライブラリを使用する典型的なNode.jsアプリケーションを考えてみましょう。

各ライブラリは潜在的に独自のロギングアプローチを持っています:

  • ExpressはDEBUG=express:*を使用
  • Socket.IOはDEBUG=socket.io:*を使用
  • Mongooseはmongoose.set('debug', true)を使用
  • Axiosはaxios-loggerや類似のパッケージを使用する可能性がある
  • Redisクライアントは独自のデバッグ設定を持つ
  • 認証ライブラリは多くの場合、独自のロギングメカニズムを含む

アプリケーション開発者の視点からすると、これは管理上の課題を生み出します。彼らは複数の異なるロギングシステムを学び、設定する必要があり、それぞれが独自の構文、機能、癖を持っています。ログは一貫性のないフォーマットで異なる出力に散らばり、アプリケーションで何が起きているかを統一的に把握することが難しくなります。

統合の欠如はまた、構造化ロギング、ログ相関、集中型ログ管理などの強力な機能が、使用中のすべてのライブラリで一貫して実装することがはるかに難しくなることを意味します。

LogTapeのアプローチ

LogTapeは、「ライブラリファーストデザイン」と呼ばれるかもしれないアプローチでこれらの課題に対処しようとしています。「ライブラリファーストデザイン」の核となる原則はシンプルですが、潜在的に強力です:ロギングが設定されていない場合、何も起こりません。出力なし、エラーなし、副作用なし—完全な透明性だけです。

このアプローチにより、ユーザーに影響を与えることなく、ライブラリに包括的なロギングを追加できます。ユーザーがあなたのライブラリをインポートしてコードを実行すると、誰かが明示的にロギングを設定するまで、LogTapeのロギング呼び出しは本質的に何もしません。ライブラリの動作に関する洞察を得たいユーザーはオプトインできます。そうでないユーザーは完全に影響を受けません。

さらに重要なことに、ユーザーがロギングを設定することを選択した場合、LogTape対応のすべてのライブラリは単一の統一された設定システムを通じて管理できます。これは、すべてのライブラリログに対して1つの一貫したAPI、1つのログフォーマット、1つの宛先を意味し、どのライブラリからどのようなログが記録されるかについて細かい制御を可能にします。

Note

このアプローチは完全に新しいものではありません—Pythonの標準loggingライブラリからインスピレーションを得ています。Pythonでは、Requests、SQLAlchemy、Djangoコンポーネントなどのライブラリはすべて標準のロギングフレームワークを使用しており、開発者は単一の一貫したシステムを通じてすべてのライブラリロギングを設定できます。これは実用的かつ強力であることが証明されており、アプリケーション開発者にとってのシンプルさを維持しながら、Pythonエコシステム全体にわたる豊かな診断機能を可能にしています。

// ライブラリコード内 - 含めても完全に安全
import { getLogger } from "@logtape/logtape";

const logger = getLogger(["my-awesome-lib", "database"]);

export function connectToDatabase(config) {
  logger.debug("データベース接続を試みています", { config });
  // ... あなたのロジック
  logger.info("データベース接続が確立されました");
}

依存関係の考慮

現代のJavaScript開発では、依存関係の慎重な検討が必要です。winstonやPinoなどの人気のあるロギングライブラリはよく保守され、広く信頼されていますが、それらは独自の依存関係ツリーを持っています。例えば、winstonには17の依存関係があり、Pinoには1つあります。

ライブラリ作者にとって、これは考慮事項を生み出します:あなたが追加するすべての依存関係は、ユーザーがそれを望むかどうかに関わらず、ユーザーにとっての依存関係になります。これは必ずしも問題ではありませんが(多くの優れたライブラリには依存関係があります)、ユーザーに代わって行う選択を表しています。

LogTapeは依存関係ゼロという異なるアプローチを取っています。これは単なる哲学的な選択ではなく、ライブラリのユーザーにとって実用的な意味を持ちます。彼らはnode_modulesに追加のパッケージを見ることはなく、ロギング関連の依存関係のサプライチェーンの考慮事項を心配する必要がなく、あなたのロギング選択と彼らのロギング選択の間の潜在的なバージョン競合に直面することもありません。

圧縮・最小化後わずか5.3KBで、LogTapeはバンドルに最小限の重さを追加します。インストールプロセスが速くなり、依存関係ツリーがよりクリーンに保たれ、セキュリティ監査はライブラリのコア機能に直接役立つ依存関係に焦点を当てたままになります。

互換性チェーンの打破

ここで、おそらく馴染みのある課題があります:あなたのライブラリがESMCommonJSの両方の環境をサポートしたいと考えています。おそらく、一部のユーザーはCommonJSに依存するレガシーNode.jsプロジェクトで作業している一方で、他のユーザーは最新のESMセットアップを使用しているか、ブラウザ向けに構築しています。

依存関係がある場合、この課題は明らかになります。ESMモジュールは問題なくCommonJSモジュールをインポートできますが、その逆は真ではありません—CommonJSモジュールはESM専用パッケージをrequireできません(少なくともNode.js 22+の実験的機能が安定するまでは)。これにより非対称な互換性制約が生じます。

あなたのライブラリがESM専用パッケージに依存している場合、CommonJS環境ではそれを使用できないため、あなたのライブラリも事実上ESM専用になります。つまり、あなたの依存関係チェーンにESM専用の依存関係が1つでもあると、CommonJSユーザーをサポートできなくなる可能性があります。

LogTapeはESMとCommonJSの両方を完全にサポートしているため、この制限を強制する弱いリンクにはなりません。ユーザーがレガシーNode.jsプロジェクト、最先端のESMアプリケーション、またはハイブリッド環境で作業しているかどうかに関わらず、LogTapeはシームレスに彼らのセットアップに適応します。

さらに重要なことに、LogTapeがネイティブESMサポートを提供する場合(単にCommonJSとしてインポート可能であるだけでなく)、最新のバンドラーでのツリーシェイキングが可能になります。ツリーシェイキングにより、バンドラーはビルドプロセス中に未使用のコードを排除できますが、これにはESMのみが提供する静的なimport/export構造が必要です。CommonJSモジュールはESMプロジェクトにインポートできますが、最適化できない不透明なブロックとして扱われることが多く、最終バンドルに未使用のコードが含まれる可能性があります。

最小限の影響を目指すロギングライブラリにとって、この最適化機能は特にバンドルサイズが重要なアプリケーションにとって意味のあるものになります。

ユニバーサルランタイムサポート

JavaScriptエコシステムは今日、印象的な範囲のランタイム環境にまたがっています。あなたのライブラリはNode.jsサーバー、Denoスクリプト、Bunアプリケーション、Webブラウザ、またはエッジ関数で実行される可能性があります。LogTapeはポリフィル、互換性レイヤー、またはランタイム固有のコードを必要とせず、これらすべての環境で同じように動作します。

この普遍性により、ユーザーが遭遇する可能性のあるすべての環境でロギングの選択が機能するかどうかを心配するのではなく、ライブラリのコア機能に集中できます。誰かがあなたのライブラリをCloudflare Worker、Next.jsアプリケーション、またはDeno CLIツールにインポートしても、ロギング動作は一貫性があり信頼できます。

妥協のないパフォーマンス

ライブラリ作者がロギングについてよく持つ懸念の1つはパフォーマンスへの影響です。ユーザーがあなたのライブラリを高性能アプリケーションにインポートした場合はどうなりますか?メモリが制約された環境で実行している場合はどうなりますか?

LogTapeはロギングが無効になっている場合、顕著な効率性でこれに対処します。未設定のLogTape呼び出しのオーバーヘッドは事実上ゼロであり、利用可能な他のロギングソリューションの中で最も低いものの1つです。これは、有効にしないユーザーへのパフォーマンスへの影響を心配することなく、開発およびデバッグ目的でライブラリ全体に詳細なロギングを追加できることを意味します。

ロギングが有効になっている場合、LogTapeは他のライブラリよりも一貫してパフォーマンスが優れており、特に開発中に最も一般的なロギング先であるコンソール出力において優れています。

名前空間の衝突回避

同じアプリケーションを共有するライブラリは、すべてが同じ名前空間に出力するとロギングの混乱を引き起こす可能性があります。LogTapeの階層的カテゴリシステムは、ライブラリに独自の名前空間を使用するよう促すことでこれを優雅に解決します。

あなたのライブラリは["my-awesome-lib", "database"]["my-awesome-lib", "validation"]などのカテゴリを使用し、ログが他のライブラリやメインアプリケーションから明確に分離されるようにします。LogTapeを設定するユーザーは、異なるライブラリやそれらのライブラリ内の異なるコンポーネントのロギングレベルを独立して制御できます。

うまく機能する開発者エクスペリエンス

LogTapeは最初からTypeScriptで構築されているため、TypeScriptベースのライブラリは追加の依存関係や型パッケージなしで完全な型安全性を得られます。APIは自然でモダンに感じられ、テンプレートリテラルと構造化ロギングパターンの両方をサポートし、現代のJavaScript開発プラクティスとうまく統合します。

// テンプレートリテラルスタイル - 自然に感じる
logger.info`ユーザー ${userId} がアクション ${action} を実行しました`;

// 構造化ロギング - モニタリングに最適
logger.info("ユーザーアクションが完了しました", { userId, action, duration });

実用的な統合

実際にライブラリでLogTapeを使用することは、驚くほど簡単です。ロガーをインポートし、適切に名前空間化されたカテゴリを作成し、意味のある場所でログを記録するだけです。設定も、セットアップも、複雑な初期化シーケンスも必要ありません。

import { getLogger } from "@logtape/logtape";

const logger = getLogger(["my-lib", "api"]);

export async function fetchUserData(userId) {
  logger.debug("ユーザーデータを取得中", { userId });
  
  try {
    const response = await api.get(`/users/${userId}`);
    logger.info("ユーザーデータが正常に取得されました", { 
      userId, 
      status: response.status 
    });
    return response.data;
  } catch (error) {
    logger.error("ユーザーデータの取得に失敗しました", { userId, error });
    throw error;
  }
}

これらのログを見たいユーザーにとって、設定も同様に簡単です:

import { configure, getConsoleSink } from "@logtape/logtape";

await configure({
  sinks: { console: getConsoleSink() },
  loggers: [
    { category: ["my-lib"], lowestLevel: "info", sinks: ["console"] }
  ]
});

移行の橋渡し

潜在的なユーザーが既に他のロギングシステムに投資している場合、LogTapeはwinstonやPinoなどの人気ライブラリ向けのアダプターを提供しています。これにより、LogTape対応のライブラリは既存のロギングインフラストラクチャと統合でき、アプリケーションが既に使用しているシステムを通じてログをルーティングできます。

これらのアダプターの存在は、正直な真実を明らかにしています:LogTapeはまだJavaScriptエコシステムで広く採用されている標準ではありません。ほとんどのアプリケーションは依然として確立されたロギングライブラリを中心に構築されており、ユーザーにロギングアプローチを完全に再構築するよう求めることは非現実的でしょう。アダプターは実用的な妥協点を表しています—ライブラリ作者はユーザーの既存の投資と好みを尊重しながら、LogTapeのライブラリフレンドリーな設計を活用できます。

このアプローチは採用の摩擦を減らしながら、ライブラリ作者にモダンでゼロ依存のロギングAPIを提供します。おそらく時間が経つにつれて、より多くのライブラリがこのパターンを採用し、より多くの開発者がその利点を経験するにつれて、そのようなアダプターの必要性は減少するかもしれません。しかし現在のところ、それらはLogTapeのビジョンとエコシステムの現実の間の実用的な橋として機能しています。

検討する価値のある選択

最終的に、ライブラリにLogTapeを選ぶことは、ライブラリとアプリケーションの関係についての特定の哲学を表しています。それは選択肢を保ちながら機能を提供し、強制を避けながら洞察を提供することについてです。

従来のアプローチ—debugパッケージを使用するか、アプリケーション重視のロガーを使用するか、カスタムソリューションを使用するか—はそれぞれに長所があり、コミュニティに良いサービスを提供してきました。LogTapeは単に別のオプションを提供します:JavaScriptエコシステムでライブラリが占める独自の位置のために特別に設計されたものです。

ライブラリ作者にとって、このアプローチはいくつかの実用的な利点を提供するかもしれません。あなたのライブラリは開発、デバッグ、ユーザーサポートのための詳細なロギングを取得し、一方でユーザーはそれらの機能を使用するかどうか、どのように使用するかについて完全な自律性を保持します。

より広い利点は、JavaScriptエコシステム全体でより結束したロギング体験かもしれません—ライブラリがアプリケーションが採用することを選択したどのようなロギング戦略とも完全に統合される豊かな診断情報を提供できる世界です。

すべての依存関係の決定が影響を持つ世界で、LogTapeは検討する価値のあるアプローチを提供します:ユーザーの好みと既存の選択を尊重しながら、ライブラリの機能を強化する方法です。

7

1 comment

If you have a fediverse account, you can comment on this article from your own instance. Search https://hackers.pub/ap/articles/01979a7b-f451-7c4e-bcd1-206cf175844c on your instance and reply to it.

1