【Flutter】コンソールとログファイルの両方にログを出力する[logger]

flutter-logger Flutter

はじめに

loggerは絵文字つきでログが出せて直感的に使える最も便利なパッケージです。

logger | Dart package
Small, easy to use and extensible logger which prints beautiful logs.

コンソールとログファイルに同時にログを出力する方法を紹介します。

要点

LoggerのoutputにMultiOutputを使用します。

final logger = Logger(
    // MultiOutputを使用すれば、ファイルとコンソールのどちらにも出力できる
    output: MultiOutput([
      FileOutput(),
      ConsoleOutput(),
    ]));

手順

以下の3ステップを実行してください

①パッケージのインストール

以下のパッケージを追加

  • メイン
    • logger // 今回のメインパッケージ
  • サブ
    • intl // ログファイルに日付を振る際に使用
    • shared_preferences // ログレベルの管理に使用
    • path_provider // ログファイルの出力先の指定に使用
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.6
  # logger
  logger: ^2.3.0
  intl: ^0.19.0
  shared_preferences: ^2.2.3
  path_provider: ^2.1.3

②logger.dart

※FileOutputにはfileの指定が必要です。アプリを落とさずに使用する場合も考慮し、都度ファイル作成メソッドを呼び出す形にしています。

import 'dart:convert';
import 'dart:io';

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

import 'main.dart';

// LogLevelを設定しておく
bool get needDetailLog => prefs.getBool('needDetailLog') ?? false;
set needDetailLog(bool value) => prefs.setBool('needDetailLog', value);

final logger = Logger(
    filter: MyFilter(),
    printer: PrettyPrinter(
      methodCount: 3,
      errorMethodCount: 10,
      lineLength: 50,
      colors: false, // VSCode上ではfalseにしておいた方が見やすい
      printTime: true,
    ),
    // MultiOutputを使用すれば、ファイルとコンソールのどちらにも出力できる
    output: MultiOutput([
      FileOutput(file: LogUtil.makeLogFile()),
      ConsoleOutput(),
    ]));

class MyFilter extends LogFilter {
  @override
  bool shouldLog(LogEvent event) {
    // LogLevel
    if (!needDetailLog) {
      if (event.level == Level.debug) {
        return false;
      }
    }
    return true;
  }
}

class LogUtil {
  static File makeLogFile() {
    // この辺りは好みで。
    DateFormat formatter = DateFormat('yyyyMMdd');
    DateTime now = DateTime.now();
    String nowStr = formatter.format(now);
    String logFileName = '$dirPath/${nowStr}_log.txt';
    File f = File(logFileName);
    if (!f.existsSync()) {
      f.createSync(recursive: true);
    }
    return f;
  }
}

class FileOutput extends LogOutput {
  final File file;
  final FileMode mode;
  final Encoding encoding;
  IOSink? _sink;

  FileOutput({
    required this.file,
    this.mode = FileMode.writeOnlyAppend,
    this.encoding = utf8,
  });

  @override
  Future<void> init() async {
    _sink = file.openWrite(
      mode: mode,
      encoding: encoding,
    );
  }

  @override
  void output(OutputEvent event) {
    _sink?.writeAll(event.lines, '\n');
  }

  @override
  Future<void> destroy() async {
    await _sink?.flush();
    await _sink?.close();
  }
}

class ConsoleOutput extends LogOutput {
  @override
  void output(OutputEvent event) {
    for (var line in event.lines) {
      // ignore: avoid_print
      print(line);
    }
  }
}

③main.dart

※iosとandroidでディレクトリの取得メソッドが異なる点に注意が必要です。
(macosなどの場合は記載していません)

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'logger.dart';

void main() async {
  // おまじない
  WidgetsFlutterBinding.ensureInitialized();
  // SharedPreferencesのインスタンスを取得
  prefs = await SharedPreferences.getInstance();
  // logをローカルに出力する際のpathは、デバイスによって異なる。
  if (Platform.isIOS) {
    final documentsDirectory = await getApplicationDocumentsDirectory();
    dirPath = documentsDirectory.path;
  } else if (Platform.isAndroid) {
    final directory = await getExternalStorageDirectory();
    dirPath = directory!.path;
  }
  // logのpath
  logger.i(dirPath);
  runApp(const MyApp());
}

late SharedPreferences prefs;
String dirPath = '';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    // LogLevel
    needDetailLog = !needDetailLog;
    _counter++;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    // loggerにフィルターをかませれば、LogLevelの設定もできる
    logger.d('message_debug');
    logger.i('message_info');
    logger.w('message_warning');
    logger.e('message_error');

    return Scaffold(
      appBar: AppBar(
        title: const Text('title'),
      ),
      body: Center(
        child: Text('$_counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}

確認

コンソールとログファイルに出力されているログを確認してみます。

コンソール

ログファイル

ログファイルの出力先は、main.dartのmainメソッドに記載の
logger.i(dirPath);
で表示されています。

補足

2024年5月現在、PrettyPrinterのcolorsをtrueにするとVSCode上の表示がおかしくなるので、falseにしておくことをオススメします。

final logger = Logger(
    printer: PrettyPrinter(
      colors: false, // VSCode上ではfalseにしておいた方が見やすい
    ),

trueの場合

falseの場合

コメント

タイトルとURLをコピーしました