LogTape 0.6.0のリリース：何が変わったのか？

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

[LogTape]: https://logtape.org/

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

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

```ts
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"`オプションを有効にすると：

```ts
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]（英語）も参照してください。

[階層的カテゴリ]: https://logtape.org/manual/categories
[#15]: https://github.com/dahlia/logtape/issues/15

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

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

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

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

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

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

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

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

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

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

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

:::note

ただし、実際にスペース文字まで正確に一致するプロパティがある場合は、そちらを優先して探します。例えば、以下のようにログを残す場合：

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

`Hello, Alice!`ではなく`Hello, Bob!`がログに記録されます。

:::

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

[#16]: https://github.com/dahlia/logtape/issues/16

## `LogRecord.rawMessage`プロパティの追加

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

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

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

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

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

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

[`LogRecord`]: https://jsr.io/@logtape/logtape/doc/~/LogRecord
[`LogRecord.message`]: https://jsr.io/@logtape/logtape/doc/~/LogRecord.message
[`LogRecord.rawMessage`]: https://jsr.io/@logtape/logtape/doc/~/LogRecord.rawMessage
[#17]: https://github.com/dahlia/logtape/issues/17

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

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

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

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

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

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

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

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

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

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

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

[テキストフォーマッタ]: https://logtape.org/manual/formatters
[ストリームシンク]: https://logtape.org/manual/sinks#stream-sink
[ファイルシンク]: https://logtape.org/manual/sinks#file-sink
[`defaultTextFormatter`]: https://logtape.org/manual/formatters#default-text-formatter
[`ansiColorFormatter`]: https://logtape.org/manual/formatters#ansi-color-formatter
[`getDefaultTextFormatter()`]: https://logtape.org/manual/formatters#default-text-formatter-1
[`getAnsiColorFormatter()`]: https://logtape.org/manual/formatters#ansi-color-formatter-1
[最初から`TextFormatter`を実装する]: https://logtape.org/manual/formatters#fully-customized-text-formatter
[関連ドキュメント]: https://logtape.org/manual/formatters#configuring-text-formatters
[#13]: https://github.com/dahlia/logtape/issues/13

## JSRおよびnpmから入手可能

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

```bash
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
```

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

[JSR]: https://jsr.io/@logtape/logtape@0.6.0
[npm]: https://www.npmjs.com/package/@logtape/logtape/v/0.6.0