LogTape 0.6.0のリリース:何が変わったのか?

2024/09/24に公開

JavaScript及びTypeScript用のゼロ依存構造化ロギングライブラリであるLogTapeがv0.6.0をリリースしました。何が変わったのでしょうか?

親ロガーのシンクをオーバーライド可能に

LogTapeの特徴の一つは、階層的カテゴリを通じたシンクの継承です。例えば、以下のように2つのロガーを設定した場合:

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

await configure({
  sinks: {
    console: getConsoleSink(),
    file:    getFileSink("app.log"),
  },
  loggers: [
    { category: ["app"],           level: "debug", sinks: ["file"] },
    { category: ["app", "module"], level: "debug", sinks: ["console"] },
  ],
});

["app"]ロガーに残したログはapp.logファイルにのみ残りますが、["app", "module"]ロガーに残したログはapp.logファイルにも残り、コンソールにも出力されます。["app", "module"]ロガーが["app"]ロガーの子カテゴリであるため、親ロガーのシンクを継承するからです。

しかし、時にはこの動作が望ましくない場合もあります。そこでLogTape 0.6.0からは親ロガーのシンクをオーバーライドできるようになりました。例えば、以下のように子ロガーでparentSinks: "override"オプションを有効にすると:

await configure({
  sinks: { /* 省略; 上と同じ */ },
  loggers: [
    { category: ["app"],           level: "debug", sinks: ["file"] },
    { category: ["app", "module"], level: "debug", sinks: ["console"], parentSinks: "override" },
  ],
});

["app"]ロガーに残したログはapp.logファイルにのみ残り、["app", "module"]ロガーに残したログはコンソールにのみ出力されます。子ロガーである["app", "module"]ロガーが["app"]ロガーのシンクをオーバーライドしたからです。

もちろん、デフォルト値はparentSinks: "inherit"、つまり継承なので、あえてオプションを指定しなければ従来通りの動作となります。

この機能追加の背景についてさらに知りたい方は、GitHubのイシュー#15(英語)も参照してください。

メッセージテンプレートでプレースホルダーの先頭と末尾のスペースを許容

以前のバージョンまでは、以下のようにログを残す場合:

logger.info("Hello, { name }!", { name: "Alice" });

期待に反してHello, undefined!というログが残されていました。これは、プレースホルダーの{ name }にスペース文字が含まれているため、"name"プロパティを探す代わりに" name "プロパティを探すからです。つまり、以下のようにプレースホルダーのスペースを削除するか:

logger.info("Hello, {name}!", { name: "Alice" });

以下のように実際のプロパティ名にも同じようにスペースを入れる必要がありました:

logger.info("Hello, { name }!", { " name ": "Alice" });

これは厳密にはバグではありませんでしたが、コーディング習慣によっては間違いやすい動作でした。

しかし、LogTape 0.6.0からは、プレースホルダーの先頭と末尾にスペースがあってもスペースのないプロパティ名を探すようになりました。例えば、以下のようにログを残す場合:

logger.info("Hello, { name }!", { name: "Alice" });

期待通りHello, Alice!というログが残ります。

この機能追加の背景についてさらに知りたい方は、GitHubのイシュー#16(英語)も参照してください。

LogRecord.rawMessageプロパティの追加

LogRecordは、LogTapeが出力および書式化される前のログを表現するデータ型です。

従来からLogRecord.messageプロパティは存在していましたが、このプロパティにはすでにメッセージテンプレートのプレースホルダーが実際のプロパティ値に置換された後の結果が入っていました。ほとんどの場合はそれで十分ですが、ログの出力先(シンク)が別のロギングシステムである場合、元のメッセージテンプレートとプロパティ値を別々に出力し、その出力を処理するロギングシステムが直接メッセージテンプレートのプレースホルダーをプロパティ値に置換することを望む場合もあります。

LogTape 0.6.0で追加されたLogRecord.rawMessageは、まさにそのような場合のためのプロパティで、メッセージテンプレートのプレースホルダーが置換されていない元の状態がそのまま含まれています。例えば、以下のようにログを残す場合:

logger.info("Hello, {name}!", { name: "Alice" });

LogRecord.messageには["Hello, ", "Alice", "!"]という値が入る一方、LogRecord.rawMessageには"Hello, {name}!"という値が入ります。

この機能追加の背景についてさらに知りたい方は、GitHubのイシュー#17(英語)も参照してください。

内蔵テキストフォーマッタに様々な設定が可能に

テキストフォーマッタは、ストリームシンクファイルシンクなどで各ログをどのようなテキストに書式化するかを決定するインターフェースで、実際のデータ型の定義は比較的シンプルです:

export type TextFormatter = (record: LogRecord) => string;

しかし、毎回直接テキストフォーマッタを定義して使うのは面倒なので、LogTapeにはdefaultTextFormatterおよびansiColorFormatterが内蔵されており、これらを使用することができます。ただし、これまでは特に設定ができなかったため、決められた書式をそのまま受け入れなければなりませんでした。例えば、"warning"のようなログレベルをWRNという3文字の略語形式で出力することが気に入らない場合、最初からTextFormatterを実装する必要がありました。

しかし、LogTape 0.6.0からはgetDefaultTextFormatter()およびgetAnsiColorFormatter()関数を通じて、直接TextFormatterを最初から実装しなくても様々な書式設定を好みに合わせて行えるようになりました。

例えば、"warning"のようなログレベルをWという大文字1文字で表現したい場合、以下のように設定できます:

const myFormatter = getDefaultTextFormatter({ level: "L" });

またはタイムスタンプの日付と時間帯を省略して時刻のみを表示したい場合、以下のように設定できます:

const myFormatter = getDefaultTextFormatter({ timestamp: "time" });

さらに多くの書式オプションについての説明は、関連ドキュメントを参照してください。

この機能追加の背景についてさらに知りたい方は、GitHubのイシュー#13(英語)も参照してください。

JSRおよびnpmから入手可能

LogTape 0.6.0はすでにJSRおよびnpmにアップロードされていますので、今すぐダウンロードしてみてください!

deno add @logtape/logtape@0.6.0  # Deno
npm  add @logtape/logtape@0.6.0  # npm
pnpm add @logtape/logtape@0.6.0  # pnpm
yarn add @logtape/logtape@0.6.0  # Yarn
bun  add @logtape/logtape@0.6.0  # Bun

それでは、楽しいロギングを!

Discussion