Page cover image

Log Color

Setting Up Colored Logs and Saving Logs to Device Storage

Step 1: Add Dependencies

First, make sure you have the necessary dependencies in your pubspec.yaml file. Add the following dependencies:

dependencies:
  flutter:
    sdk: flutter
  logger: ^2.3.0

Step 2: Create a Logging Class

Use the provided code snippet to create a logging class. This class will handle colored logs and save logs to the device storage.

import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io';

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

bool enviIsDevelop = true;

class MyLog {
  bool isLogging = true;

  Logger logger = Logger(
    filter: kReleaseMode ? PermissiveFilter() : DevelopmentFilter(),
    printer: PrettyPrinter(
      methodCount: 3,
      printEmojis: false,
      printTime: true, // Log sẽ chứa thời gian
    ),
  );

  Future<void> setUp({
    String? path,
    bool printTime = true,
    bool isLogging = true,
    String? noteInfoFileLog,
  }) async {
    if (this.isLogging != isLogging) this.isLogging = isLogging;
    if (path != null) {
      final file = File(path);
      if (noteInfoFileLog != null) {
        await file.writeAsString(
          noteInfoFileLog,
          mode: FileMode.writeOnlyAppend,
        );
      }

      final FileOutput2 fileOutPut = FileOutput2(
        file: file,
      );
      final ConsoleOutput consoleOutput = ConsoleOutput();
      final List<LogOutput> multiOutput = [fileOutPut, consoleOutput];
      logger = Logger(
        printer: PrettyPrinter(
          methodCount: 3,
          errorMethodCount: 12,
          lineLength: 150,
          printEmojis: false,
          printTime: printTime,
        ),
        filter: kReleaseMode ? PermissiveFilter() : DevelopmentFilter(),
        output: MultiOutput(multiOutput),
      );
    } else {
      logger = Logger(
        printer: PrettyPrinter(
          methodCount: 3,
          errorMethodCount: 12,
          lineLength: 150,
          printEmojis: false,
          printTime: printTime,
        ),
        filter: kReleaseMode ? PermissiveFilter() : DevelopmentFilter(),
      );
    }
  }

  void trace(Object? object, {String? tag, String? flag, Object? error}) {
    if (kDebugMode || isLogging) {
      logger.t(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: error,
      );
    }
  }

  void debug(Object? object, {String? tag, String? flag, Object? error}) {
    if (kDebugMode || isLogging) {
      logger.d(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: error,
      );
    }
  }

  void info(Object? object, {String? tag, String? flag, Object? error}) {
    if (kDebugMode || isLogging) {
      logger.i(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: error,
      );
    }
  }

  void warning(Object? object, {String? tag, String? flag, Object? error}) {
    if (kDebugMode || isLogging) {
      logger.w(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: error,
      );
    }
  }

  void error(Object? object, {String? tag, String? flag}) {
    if (kDebugMode || isLogging) {
      logger.e(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: object,
      );
    }
  }

  void fatal(Object? object, {String? tag, String? flag, required Object error, StackTrace? stackTrace}) {
    if (kDebugMode || isLogging) {
      logger.f(
        _print(
          object,
          tag: tag ?? _getParentMethodName(),
          flag: flag,
        ),
        time: DateTime.now(),
        error: error,
        stackTrace: stackTrace,
      );
    }
  }
}

String? _getParentMethodName() {
  try {
    throw Exception();
  } catch (e, stackTrace) {
    final frames = stackTrace.toString().split('\n');

    var father = "";
    var grandfather = "";

    try {
      final fatherFrame = frames[3];
      father = fatherFrame.split(' ').last;
    } catch (_) {}
    try {
      final grandfatherFrame = frames[4];
      grandfather = grandfatherFrame.split(' ').last;
    } catch (_) {}

    return father == "" && grandfather == "" ? null : "$father${grandfather == "" ? grandfather : "\n$grandfather"}\n";
  }
}

String _print(Object? object, {String? tag, String? flag}) =>
    (tag == null || tag == "") && (flag == null || flag == "")
        ? object.toString()
        : tag != null && tag != "" && flag != null && flag != ""
            ? "$tag | ${flag.toUpperCase()} | $object"
            : (tag == null || tag == "") && flag != null && flag != ""
                ? "${flag.toUpperCase()} | $object"
                : "$tag | $object";

class FileOutput2 extends LogOutput {
  final File file;
  final bool overrideExisting;
  final Encoding encoding;
  late IOSink _sink;

  FileOutput2({
    required this.file,
    this.overrideExisting = false,
    this.encoding = utf8,
  });

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

  @override
  void output(OutputEvent event) {
    try {
      if (event.origin.level != Level.trace) _sink.writeAll(["${event.origin.level.name} | ${event.origin.time} | ${event.origin.error} \n${event.origin.message} \n\n"], '\n');
    } catch (e) {
      print(e);
    }
  }

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

void myDebugger(Object? object, {String? tag, String? flag, bool when = true}) {
  developer.debugger(
    when: when,
    message: _print(
      object,
      tag: tag ?? _getParentMethodName(),
      flag: flag,
    ),
  );
}

Explanation

Function: _getParentMethodName()

_getParentMethodName

This function retrieves the location of the log within the project, similar to how error logs display stack traces. This makes it easier to navigate directly to the log’s source in the code, reducing search time.

String? _getParentMethodName() {
  try {
    throw Exception();
  } catch (e, stackTrace) {
    final frames = stackTrace.toString().split('\n');

    var father = "";
    var grandfather = "";

    try {
      final fatherFrame = frames[3];
      father = fatherFrame.split(' ').last;
    } catch (_) {}
    try {
      final grandfatherFrame = frames[4];
      grandfather = grandfatherFrame.split(' ').last;
    } catch (_) {}

    return father == "" && grandfather == "" ? null : "$father${grandfather == "" ? grandfather : "\n$grandfather"}\n";
  }
}

How It Works:

  1. It generates an exception and captures the stack trace.

  2. It extracts the method names from specific frames in the stack trace.

  3. It returns the method names in a structured format, making it easier to locate the log’s origin.

This helps in debugging by directly pinpointing where the log was recorded.


Function: _print()

This function customizes log display by adding tags and flags for better classification.

String _print(Object? object, {String? tag, String? flag}) =>
    (tag == null || tag == "") && (flag == null || flag == "")
        ? object.toString()
        : tag != null && tag != "" && flag != null && flag != ""
            ? "$tag | ${flag.toUpperCase()} | $object"
            : (tag == null || tag == "") && flag != null && flag != ""
                ? "${flag.toUpperCase()} | $object"
                : "$tag | $object";

Usage Example:

If the log is related to an Activity Diagram, you might format it as:

print(_print("get image screenshot", tag: "ADS 4.4.6.0", flag: "ACTIVITY DIAGRAM"));

Output:

ADS 4.4.6.0 | ACTIVITY DIAGRAM | get image screenshot

How It Works:

  • If both tag and flag are provided → Outputs tag | FLAG | message

  • If only flag is provided → Outputs FLAG | message

  • If only tag is provided → Outputs tag | message

  • If neither is provided → Retrieves the parent method’s name

Step 3: Initialize Logging in Your App

Set up the logging system in your main application file. Ensure you call the setUp method with the desired parameters.

const ourLogDiagram = "Activity Diagram";

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  MyLog myLog = MyLog();
  await myLog.setUp(
    path: 'path/to/your/logfile.txt', // Specify the path where you want to save the log file
    printTime: true,
    isLogging: true,
    noteInfoFileLog: 'This is the log file for my Flutter app.',
  );

  // Example log usage
  myLog.info('App started');
  myLog.debug('Debugging info');
  myLog.error('An error occurred');
  myLog.info("ADS 4.4.7 | save image", flag: ourLogDiagram, tag: "Write log");

  runApp(MyApp());
}

Step 4: Use Logging Methods

Use the logging methods provided in the MyLog class throughout your application to record messages at different levels (trace, debug, info, warning, error, fatal).

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    MyLog myLog = MyLog();
    
    myLog.info('Building MyApp');
    
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Logging Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              myLog.debug('Button pressed');
              myLog.warning('This is a warning');
              myLog.error('This is an error');
            },
            child: Text('Press Me'),
          ),
        ),
      ),
    );
  }
}

Check Logs on the Device

After running the app and generating logs, you can find the log file at the specified path (e.g., path/to/your/logfile.txt). Open the file to see the stored logs.

If you want to skip the complex parts and use it right away, check out my package: https://pub.dev/packages/my_log.

Buy Me a Coffee | Support Me on Ko-fi

Last updated