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()

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:
It generates an exception and captures the stack trace.
It extracts the method names from specific frames in the stack trace.
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.
Last updated