🦄

【2022年】おすすめのロガーパッケージ4選【Flutter】

2022/04/15に公開

はじめに

みなさんはどのロガー(ログ出力)パッケージを使っていますか?あるいは自作していますか?

本記事では Flutter 開発でかかせないおすすめのロガーパッケージ4選を紹介していきます。おまけで自作ロガーのサンプル実装も紹介しています。是非参考にしてください!

自分が欲しい機能を実装したロガーパッケージを作成して pub.dev に公開しました!是非こちらもご覧下さい!

https://zenn.dev/susatthi/articles/20220506-144617-flutter-roggle

修正履歴

2022/05/06 はじめにに、ロガーパッケージ作成したリンクを追加しました。
2022/04/16 logger と logging について throw AssertionError() すると処理が止まると記載しておりましたが、しっかり確認したら処理が止まりませんでしたので記事を修正しました。

ところで、わざわざロガーパッケージなんか使わず print でよくない?と思われる方もいるかもしれません。しかし、print はリリースビルドでも出力されてしまうためコミットは控えた方が良いです。さらに、多くのロガーパッケージに比べて次の点で劣ります。

  • ログレベル(infowarning など)によるログ出力のフィルタができない。
  • ログ出力した場所(ファイル名や行)や時刻情報などの付加情報が出力されない。
  • Crashlytics にエラーを送信するなどの処理を挿入できない。

ロガーパッケージにはそれぞれ特徴がありますので、本記事を参考にして自分にあったロガーを見つけてみてください。

環境

Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision c860cba910 (3 weeks ago) • 2022-03-25 00:23:12 -0500
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2

おすすめのロガー4選

logger 1.1.0

https://pub.dev/packages/logger

pub.dev 👍 1417
GitHub ⭐ 911

ロガーパッケージの中でダントツの指示を得ています。デフォルトでリリースビルドは出力しない動作になるなど、すぐに使えて高機能なロガーです。ログレベルによって出力するログが色づけされていて美しく見やすいです。高い拡張性も備えています。

基本的な使い方

import 'package:logger/logger.dart';

// デフォルトでリリースビルドは出力しない、デバッグビルドはすべてログ出力する動作になっている
final logger = Logger();

void main() {
  logger.v('Hello logger!');
  logger.d(1000);
  logger.i(true);
  logger.w([1, 2, 3]);
  logger.e({'key': 'key', 'value': 'value'});
  logger.wtf(Exception('例外もいけます'));
  logger.i(() => '関数もいけます');
}

logger

応用例

import 'package:intl/intl.dart';
import 'package:logger/logger.dart';

final logger = Logger(
  // ログ出力内容をカスタマイズできる
  // あらかじめ用意されているPrettyPrinterをカスタマイズした例
  printer: PrettyPrinter(
    methodCount: 1, // 表示されるコールスタックの数
    errorMethodCount: 5, // 表示されるスタックトレースのコールスタックの数
    lineLength: 120, // 出力するログ1行の幅
    colors: true, // メッセージに色をつけるかどうか
    printEmojis: true, // 絵文字を出力するかどうか
    printTime: true, // タイムスタンプを出力するかどうか
  ),
);

// 別インスタンスのロガーを作ることもできる
final logger2 = Logger(
  // ログ出力内容をシンプルにカスタマイズした例
  printer: MyLogPrinter(),

  // ログ出力したあとに処理を挿入する
  output: MyLogOutput(),
);

/// シンプルな出力
class MyLogPrinter extends LogPrinter {
  
  List<String> log(LogEvent event) {
    final message = event.message;

    String msg;
    if (message is Function()) {
      msg = message().toString();
    } else if (message is String) {
      msg = message;
    } else {
      msg = message.toString();
    }
    return [
      '[${event.level.name.toUpperCase()}] '
          '${DateFormat('HH:mm:ss.SSS').format(DateTime.now())}: '
          '$msg'
    ];
  }
}

class MyLogOutput extends ConsoleOutput {
  
  void output(OutputEvent event) {
    super.output(event);
    if (event.level.index >= Level.error.index) {
      // 致命的なエラーが発生したのでAssertionErrorをthrowしてStackTraceを表示する
      throw AssertionError('View stack trace by logger');
    }
  }
}

void main() {
  logger.i('Hello logger!');
  logger.w('Hello logger warning!', Exception('loggerの例外'), StackTrace.current);
  logger2.i('Hello logger!');
  logger2.e(Exception('例外を投げてみる'));
  logger2.i('処理は止まらない');
}

logger

logging 1.0.2

https://pub.dev/packages/logging

pub.dev 👍 312
GitHub ⭐ 213

標準パッケージです。非常にシンプルで、ログ出力部分を自分で実装する必要があります。すべてを自分好みに実装したい方におすすめです。

基本的な使い方

import 'package:logging/logging.dart';

final logger = Logger('MyLogger');

void main() {
  // すべてログ出力する
  Logger.root.level = Level.ALL;

  // ログ出力内容を定義する(実装必須)
  Logger.root.onRecord.listen((LogRecord rec) {
    print('[${rec.loggerName}] ${rec.level.name}: ${rec.time}: ${rec.message}');
  });

  logger.finer('Hello logger!');
  logger.fine(1000);
  logger.config(true);
  logger.info([1, 2, 3]);
  logger.warning({'key': 'key', 'value': 'value'});
  logger.severe(Exception('例外もいけます'));
  logger.shout(() => '関数もいけます');
}

logging

応用例

import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';

final logger = Logger('MyLogger');

// 別インスタンスのロガーを作ることもできる
final logger2 = Logger('MyLogger2');

void main() {
  // リリースビルドは出力しない、デバッグビルドはすべてログ出力する
  Logger.root.level = kReleaseMode ? Level.OFF : Level.ALL;

  // ログ出力内容を定義する(実装必須)
  Logger.root.onRecord.listen((LogRecord rec) {
    print('[${rec.loggerName}] ${rec.level.name}: ${rec.time}: ${rec.message}');
    if (rec.level >= Level.SEVERE) {
      // 致命的なエラーが発生したのでAssertionErrorをthrowしてStackTraceを表示する
      throw AssertionError('View stack trace by logger');
    }
  });

  logger.info('Hello logger!');
  logger2.info('ロガーを切り替えることが出来ます。');
  logger.severe(Exception('例外を投げてみる'));
  logger.info('処理は止まらない');
}

logging

simple_logger 1.9.0

https://pub.dev/packages/simple_logger

pub.dev 👍 62
GitHub ⭐ 45

すぐに使い始められるシンプルなロガーです。標準の logging パッケージ とインターフェースを揃えているため乗り換えが簡単です。作者の mono さんが simple_logger の詳細について解説してくれています。

https://medium.com/flutter-jp/logger-ec25d8dd179a

基本的な使い方

import 'package:simple_logger/simple_logger.dart';

final logger = SimpleLogger()
  ..setLevel(
    // すべてログ出力する
    Level.ALL,
    // ログ出力した場所を出力する
    includeCallerInfo: true,
  );

void main() {
  logger.finer('Hello logger!');
  logger.fine(1000);
  logger.config(true);
  logger.info([1, 2, 3]);
  logger.warning({'key': 'key', 'value': 'value'});
  logger.severe(Exception('例外もいけます'));
  logger.shout(() => '関数もいけます');
}

simple_logger

応用例

import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:simple_logger/simple_logger.dart';

final logger = SimpleLogger()
  ..formatter = (info) {
    // ログ出力内容をカスタマイズできる
    return '[${info.level}] '
        '${DateFormat('HH:mm:ss.SSS').format(info.time)} '
        '[${info.callerFrame ?? 'caller info not available'}] '
        '${info.message}';
  }
  ..setLevel(
    // リリースビルドは出力しない、デバッグビルドはすべてログ出力する
    kReleaseMode ? Level.OFF : Level.ALL,
    // ログ出力した場所を出力する
    includeCallerInfo: true,
  )
  ..onLogged = (log, info) {
    if (info.level >= Level.SEVERE) {
      // 致命的なエラーが発生したのでAssertionで止めるとStackTraceも表示される
      throw AssertionError('Stopped by logger');
    }
  };

void main() {
  logger.info('Hello logger!');
  logger.severe(Exception('例外を投げてみる'));
}

simple_logger

loggy 2.0.1+1

https://pub.dev/packages/loggy

pub.dev 👍 44
GitHub ⭐ 65

高い拡張性を備えたロガーです。ロガータイプを定義して mixin を使用することで、ログの出力内容を変えることができるのが特徴的です。リリースビルドではログ出力せずに Crashlytics にエラーを送信することができます。

基本的な使い方

import 'package:loggy/loggy.dart';

final logger = Loggy('MyLogger');

void main() {
  Loggy.initLoggy();

  logger.debug('Hello logger!');
  logger.info(1000);
  logger.warning(true);
  logger.error([1, 2, 3]);
  logger.info({'key': 'key', 'value': 'value'});
  logger.info(Exception('例外もいけます'));
  logger.info(() => '関数もいけます');
}

loggy

応用例

import 'package:flutter/foundation.dart';
import 'package:loggy/loggy.dart';

final logger = Loggy('MyLogger');

/// Crashlyticsにエラーを送信するプリンター
class CrashlyticsPrinter extends LoggyPrinter {
  const CrashlyticsPrinter() : super();

  
  void onLog(LogRecord record) {
    if (record.level.priority >= LogLevel.error.priority) {
      // Crashlyticsにエラーを送信する例
      // FirebaseCrashlytics.instance.log(record.message);
      // FirebaseCrashlytics.instance.recordError(record.error, record.stackTrace);
    }
  }
}

void main() {
  Loggy.initLoggy(
    // リリースビルドは出力せずに Crashlytics にエラーを送信し、デバッグビルドはログ出力する
    logPrinter:
        kReleaseMode ? const CrashlyticsPrinter() : const PrettyPrinter(),
    logOptions: const LogOptions(
      // すべてのログを出力する
      LogLevel.all,
      // エラーの場合はスタックトレースも出力する
      stackTraceLevel: LogLevel.error,
      // ログ出力した場所を出力する
      includeCallerInfo: true,
    ),
  );

  // mixinを使った例
  SampleUi();
  SampleNetwork();

  logger.info('Hello logger!');
  logger.error(Exception('例外を投げてみる'));
}

class SampleUi with UiLoggy {
  SampleUi() {
    loggy.info('This is info message');
  }
}

class SampleNetwork with NetworkLoggy {
  SampleNetwork() {
    loggy.info('This is info message');
  }
}

loggy

【おまけ】dart:developer を使って自作

Flutter SDK にある dart:developer を使ってロガーを自作した簡単なサンプルです。他のロガーに出る I/flutter (27516): の部分が出力されません(地味にうれしい人がいるかもしれません)。

サンプル実装

import 'dart:developer' as developer;

import 'package:flutter/foundation.dart';

final logger = Develogger();

class Develogger {
  void log(Object? message) {
    String msg;
    if (message is Function()) {
      msg = message().toString();
    } else if (message is String) {
      msg = message;
    } else {
      msg = message.toString();
    }

    // リリースビルドは出力しない、デバッグビルドは出力する
    if (!kReleaseMode) {
      developer.log(msg, name: 'Develogger');
    }
  }
}

void main() {
  logger.log('Hello logger!');
  logger.log(1000);
  logger.log(true);
  logger.log([1, 2, 3]);
  logger.log({'key': 'key', 'value': 'value'});
  logger.log(Exception('例外もいけます'));
  logger.log(() => '関数もいけます');
}

develogger

まとめ

紹介した4つのロガーパッケージの比較表になります。

logger logging simple_logger loggy
pub.dev 👍 数 1417 312 62 44
GitHub ⭐ 数 911 213 45 65
ログレベル数 6種類
verbose
debug
info
warning
error
wtf
7種類
finer
fine
config
info
warning
severe
shout
7種類
finer
fine
config
info
warning
severe
shout
4種類
debug
info
warning
error
ログ出力内容の変更
ログレベル毎のフィルタ
任意の処理を実行[1]
リリースビルド対応[2]
導入の容易さ ×
複数インスタンス ×
エラーログで処理停止[3] × × ×
特徴 高機能
高い拡張性
美しいログ
標準パッケージ
極限までシンプル
シンプルで使いやすい
標準パッケージと同じI/F
高い拡張性
mixinで利用可能
Crashlytics対応[4]

今回のロガーを調査したリポジトリを公開しておきます。

https://github.com/susatthi/flutter-sample-logger

最後に

Flutter 大学という Flutter エンジニアに特化した学習コミュニティに所属しています。オンラインでわいわい議論したり、Flutter の最新情報をゲットしたりできます!ぜひ Flutter 界隈を盛り上げていきましょう!

https://flutteruniv.com?invite_id=9hsdZHg0qtaMIr6RPRulAaRJfA83

あわせて読みたい

https://rizumita.medium.com/logging-in-dart-dd9c01eb459a

https://zenn.dev/popy1017/articles/a0084b287e8d53e7b730

脚注
  1. ログ出力後に任意の処理を実行できること ↩︎

  2. リリースビルド時にログ出力停止できること ↩︎

  3. デバッグビルド時に AssertionError を throw して処理を停止できること ↩︎

  4. リリースビルド時にログ出力停止しつつCrashlyticsにログを送信可能であること ↩︎

Discussion