Dartで始めるロギング生活

# Dartでのログ出力方法

Dartでログ出力を行う方法としては、まず`print`関数があります。これはdart:coreパッケージに入っており、`import`無しで使用可能で、クライアントとWebの両方で使用することができます。

- [print function - dart:core library - Dart API](https://api.dartlang.org/1.13.0/dart-core/print.html)

```dart:printの例
main() {
  print('hoge');
}
```

次にWeb向けであればdart:htmlの`console`を使うことができます。これはJavaScriptから使えるものと同じものです。

- [Console class - dart:html library - Dart API](https://api.dartlang.org/1.13.0/dart-html/Console-class.html)

```dart:consoleの例
import 'dart:html';

main() {
  console.log('hoge');
  console.info('foo');
  console.debug('bar');
}
```

しかし、これらだけでは物足りないことがあります。例えば、

- ログレベルをコントロールしたい
- ログをまとめて保存したり送信したりしたい

これらをしようとすると、独自の仕組みを作ってあげる必要があります。

そんなときには、Dartチーム謹製の公式パッケージ Logging が役に立ちます。

# Loggingを使ってログ出力

- [logging 0.11.2 | Pub Package Manager](https://pub.dartlang.org/packages/logging)

このパッケージは、始めに「ログをどう出力するか」を設定して上げる必要があります。

```dart:ログレベルの指定とログ出力方法の設定の例
import 'package:logging/logging.dart';

final log = new Logger('hoge');

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });
}
```

`Logger.root.level`で、先に出力するログレベルを指定します。上の例では、すべてのログを出力するようになります。

そして、`Logger.root.onRecord.listen`によって、どのようにログ出力するか、を書く必要があります。上の例では`print`によって出力するようになっています。

# Loggingの設計

このLoggingの設計は、Closure JSのLogger、java.util.logging.Loggerに似せて作られたようです。

- [Logger (Java Platform SE 6)](https://docs.oracle.com/javase/jp/6/api/java/util/logging/Logger.html)

用意されているログ出力用のメソッド、ログレベルの定義がほぼ同じになっています。

# ログ出力

## Loggerのインスタンスを作る

```dart:Loggerのインスタンスを作る例
import 'package:logging/logging.dart';

final log = new Logger('hoge');

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });
}
```

このサンプルコードでは`final log = new Logger('hoge')`として、ログ出力用のLoggerのインスタンスを作成していました。これを使用してログ出力することができます。

この`Logger`コンストラクタの第一引数に`String`で名前をあてるのが必須になり、この名前はユニークな名前である必要があります。

## ログ出力用メソッド

`Logger`には、以下のログ出力用メソッドがあります。

|ログ出力メソッド|説明|
|---|---|
|config(message, [Object error, StackTrace stackTrace]) → void|Level.CONFIGのログを出力する|
|fine(message, [Object error, StackTrace stackTrace]) → void|Level.FINEのログを出力する|
|finer(message, [Object error, StackTrace stackTrace]) → void|Level.FINERのログを出力する|
|finest(message, [Object error, StackTrace stackTrace]) → void|Level.FINESTのログを出力する|
|info(message, [Object error, StackTrace stackTrace]) → void|Level.INFOのログを出力する|
|severe(message, [Object error, StackTrace stackTrace]) → void|Level.SEVEREのログを出力する|
|shout(message, [Object error, StackTrace stackTrace]) → void|Level.SHOUTのログを出力する|
|warning(message, [Object error, StackTrace stackTrace]) → void|Level.CONFIGのログを出力する|
|log(Level logLevel, message, [Object error, StackTrace stackTrace, Zone zone]) → void|ログレベルを指定してログを出力する|

```dart:ログ出力の例
import 'package:logging/logging.dart';

final log = new Logger('hoge');

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });

  String result = '何らかの結果';
  log.fine('結果が得られました: $result');

  result = null;
  if (result == null) {
    log.severe('何の成果も得られませんでした!!');
  }
}
```

## Loggerのインスタンスを複数作る

`Logger`のインスタンスを複数作ることで、用途の違うログ出力を分けることができます。

```dart:ログ種類を分けた例
// レンダリング関連のログ
final renderLog = new Logger('render');

// ゲームロジックのログ
final gameLog = new Logger('game');
```

各Loggerインスタンスごとに出力するログレベルを変える場合は、`hierarchicalLoggingEnabled`を`true`にする必要があります。

```dart:ログ種類ごとに出力するログレベルを設定する例
import 'package:logging/logging.dart';

// レンダリング関連のログ
final renderLog = new Logger('render');

// ゲームロジックのログ
final gameLog = new Logger('game');

main() {
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });

  hierarchicalLoggingEnabled = true;

  // レンダリング関連のログを出力しない
  renderLog.level = Level.OFF;
}
```



# ログレベル

ログレベルは、LoggingパッケージのLevelクラスによって定義されていて、このログレベルは`Logger.root.level`に設定することで、出力するログレベルをコントロールすることができます。

- [Level class - logging library - Dart API](https://www.dartdocs.org/documentation/logging/0.11.2/logging/Level-class.html)

|ログレベル|説明|Level.value|
|---|---|---|
|OFF|全てのログ無し|2000|
|SHOUT|デバッグ時に目立つ騒がしいログ|1200|
|SEVERE|深刻な障害情報|1000|
|WARNING|問題の可能性があるログ|900|
|INFO|情報提供|800|
|CONFIG|コンフィグ設定のメッセージ|700|
|FINE|トレース用情報|500|
|FINER|そこそこ詳細なトレース用情報|400|
|FINEST|かなり詳細なトレース用情報|300|
|ALL|すべてのログ|0|

上記で設定したログレベルよりもLevel.valueが大きいログが出力されるようになります。

例えば、`OFF`に設定した場合は、`2000`より大きいログが無い場合、ログは何も出なくなります。

`INFO`に設定した場合は、`INFO`,`SEVERE`,`SHOUT`のログが出るようになります。

```dart:ログの出力レベルを変更する例
main() {
  Logger.root.level = Level.ALL; // すべてのログを出力する
  Logger.root.level = Level.OFF; // すべてのログを出力しない
  Logger.root.level = Level.INFO; // INFO以上のログを出力する
}
```

# ログ出力先をカスタマイズする

上記の例では、ログを`print'で出力していました。

```dart:ログ出力方法の設定の例
import 'package:logging/logging.dart';

final log = new Logger('hoge');

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });
}
```

これを、例えば`List`に入れることによって、あとからログを送信する、などと言ったことも可能になります。

```dart:ログ出力をprintと合わせてListに入れる例
import 'package:logging/logging.dart';

final log = new Logger('hoge');
final logs = <String>[];

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    var message = '${rec.level.name}: ${rec.time}: ${rec.message}';
    print(message);
    logs.add(message);
  });
}
```

ログはデバッグで役立つので、Loggingパッケージを活用していきたいです。