
# SpringLog

`SpringLog` は名前付きのロガーです。その静的メンバーは、レベル、ハンドラー、リスナーといったロギング全体の設定を担います。

## 使う理由

コードは `SpringLog` を通してログを出し、レコードの出力先はバックエンドが決めます。これでライブラリとアプリケーションが同じ API を使い、コードを変えずにロガー名ごとのレベルを設定できます。

- ロガーはクラスに `@Log()` を付けて得るか、定数として宣言します: `const log = SpringLog('neon.checkout');`。`const` のロガーは、ログを出すまでコストがかかりません。
- レベルは名前のドットに従います。`neon.checkout.payment` は、自身のレベルが設定されていればそれを、なければ `neon.checkout`、次に `neon`、最後にルートのレベルを使います。
- ルートのレベルは、変更しなければ `info` です。
- バックエンドがない場合、`printLogHandler` が各レコードを 1 行で書きます: `[logger] LEVEL: message`。エラーがあればその後に続きます(スタックトレースは出力しません)。出力先は Dart VM では標準出力、Flutter ではデバイスログ、Web ではブラウザーのコンソールです。ターミナルでは行に色が付きます。

## 例

```dart
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:neon/spring_gen/app.spring.dart'; // 生成されたファイル

@Service()
@Log(topic: 'neon.checkout') // ロガー名を明示的に指定
class Checkout {
  Checkout(this.gateway);
  final PaymentGateway gateway;

  void pay(Order order) {
    log.info('order placed', fields: {'order': order.id});
    log.debug(() => 'basket: ${order.dump()}'); // 有効なときだけ組み立てる
    try {
      gateway.charge(order);
    } on PaymentException catch (e, s) {
      log.error('payment failed', error: e, stackTrace: s);
    }
  }
}

// `dart:math` も `log` を宣言しているので、そこでは別のゲッター名を選ぶ。
@Component()
@Log(fieldName: 'logger') // ロガー 'neon.Payments'
class Payments {
  void refund() => logger.warning('refund');
}
```

コードでのレベル、フィールド、リスナー:

```dart
const log = SpringLog('neon.jobs');

void configureLogging() {
  SpringLog.level = SpringLogLevel.warning; // ルートのレベル
  SpringLog.setLevel('neon.checkout', SpringLogLevel.debug); // その配下も
}

Future<void> runJob(String jobId) =>
    // 内部で出したすべてのレコード(`await` の後も含む)に `job` が付く。
    SpringLog.withFields({'job': jobId}, () async {
      log.info('started');
      await Future<void>.delayed(const Duration(seconds: 1));
      log.info('finished');
    });

void captureInTest() {
  final records = <SpringLogRecord>[];
  final stop = SpringLog.listen(records.add); // ハンドラーの後に呼ばれる
  log.warning('disk almost full');
  stop();
}
```

`@Log` は、そのクラスをスキャンする `@ComponentScan` の `.spring.dart` ファイルに `log` ゲッターを書きます。クラスのライブラリでそのファイルをインポートしてください。デフォルトのロガー名は `<package>.<Class>` です。

## メンバー

`@Log`:

| 名前 | 型 | デフォルト | 意味 |
|---|---|---|---|
| `topic` | `String?` | `null` | ロガー名。`null` なら `<package>.<Class>`。 |
| `fieldName` | `String` | `'log'` | 生成されるゲッターの名前。 |

`SpringLog` のインスタンスメンバー:

| 名前 | 意味 |
|---|---|
| `SpringLog(name)` | ロガーを作る。通常は `const`。 |
| `name` | ロガー名。レベルの検索に使う。 |
| `trace(message, {fields})`、`debug(...)`、`info(...)` | そのレベルでログを出す。 |
| `warning(message, {error, stackTrace, fields})`、`error(...)` | そのレベルでログを出す。エラーとスタックトレースは省略可能。 |
| `log(level, message, {error, stackTrace, fields})` | 実行時に選んだレベルでログを出す。 |
| `isEnabled(level)` | このロガーが現在 `level` のレコードを出すか。 |

`message` にはテキストを返す関数も渡せます。その関数はレコードが出力されるときだけ呼ばれます。それ以外のオブジェクトは `toString` でテキストになります。

`SpringLog` の静的メンバー:

| 名前 | 意味 |
|---|---|
| `level` | ルートのレベル。設定しなければ `SpringLogLevel.info`。 |
| `setLevel(name, level)` | ロガーとその配下のレベルを設定する。`null` で再び継承に戻る。 |
| `levelOf(name)` | ロガー名に適用されるレベル。 |
| `configuredLevels`、`resetLevels()` | `setLevel` で設定したレベル。すべて削除する。 |
| `handler` | レコードの出力先(`SpringLogHandler`)。バックエンドが置き換えなければ `printLogHandler`。 |
| `listen(listener)` | 出力されるすべてのレコードを `listener` にも送る。停止する関数を返す。 |
| `withFields(fields, body)` | `body` の中で出したすべてのレコード(非同期処理も含む)に `fields` を加えて実行する。 |
| `currentFields` | 現在実行中のコードに `withFields` が設定したフィールド。 |
| `hold()` | 返された `SpringLogHold` で `release()` するまでレコードを保留する。レベルが決まる前の起動コードが使う。 |

`SpringLogLevel`: `trace`、`debug`、`info`、`warning`、`error`、およびロガーを無効にする `off`。

`SpringLogRecord`(ハンドラーとリスナーが受け取るもの): `level`、`loggerName`、`message`、`error`、`stackTrace`、`fields`、`time`、`sequence`。

その他のヘルパー: `printLogHandlerColours` は色付けを強制的にオン/オフします。`parseSpringLogLevel(text)` は設定に書かれたレベル名を読みます(`warn` と `warning` のどちらも可)。`LogFormatUtils.formatValue(value)` は外部から来た値を引用符で囲み、改行や制御文字を置き換えて、ログ行を偽造できないようにします。

## Spring (Java) との対応

```java
// Spring's commons-logging facade
private static final Log log = LogFactory.getLog(Checkout.class);
log.info("order placed");

// Lombok
@Slf4j(topic = "neon.payments")
class Payments { void refund() { log.warn("refund"); } }
```

重要な違い:

- `@Log` は Lombok の `@Slf4j` に当たりますが、ゲッターはコンパイラープラグインではなくビルドで生成されます。
- レベル名は `warn` ではなく `warning` です(設定ではどちらも使えます)。
- `withFields` は SLF4J の MDC に当たりますが、スコープはスレッドではなく Dart のゾーンなので、`async` のコードにも引き継がれます。
- レコードは `SpringLog.handler` が配送します。Logback や Log4j の設定ファイルはありません。Spring Boot を使う場合、レベルは `logging.level.<name>` プロパティから設定されます。

## 関連項目

- [Logging](index.md)
- [@Component](../beans/component.md)
- [Environment](../config/environment.md)
