Logger for Flutter: how to log your messages in Flutter
A simple package to log your message with low effort
Logs are useful to know about the state of out App. When we are coding new features, logs are essentials, and they also are important when our App is on a production environment and we want to know about the App state into an specific user.
What should we expect for a Logger?
There are common features that a logger has to have:
- Write to the console or to some file or database
- Write with some kind of level: info, debug, error
- Have a date/time property to know when the message has been written
- Be accessible through all the code
- Be simple
I have written a package that resolves the common logger features.
About FionaLogger
Fiona is my cat so it is not the logger of the Shrek’s girlfriend. FionaLogger contains a few classes that consider those 5 points listed before.
Let’s see a graphic with the involved classes:
- FionaLogger: is our logger. It contains methods for write a message with differents levels (v: verbose, w:warning, i:info, d:debug and e:error).
- LogData: is our log entity. It has the level, the datetime and the message.
- LogDataRepository: determines where the message will be posted/saved. This entity opens the package to be customized. You will be able to extend it and save your message where ever you want.
LogData
import 'package:fiona_logger/src/log/fiona_logger.dart';
import 'package:intl/intl.dart';
class LogData{
String message="";
String level;
int datetime=DateTime.now().toUtc().millisecondsSinceEpoch;
int id=0;
LogData(this.message, {this.id=0, required this.level});
LogData.fromMap(Map<String, dynamic> item):level=Level.verbose.toString() {
id=item["id"];
message=item["message"];
datetime=item["datetime"];
}
Map<String, Object> toMap(){
return {'id': id, 'message': message, 'datetime': datetime};
}
String getFormattedDateTime(){
var date = DateTime.fromMillisecondsSinceEpoch(datetime);
return DateFormat('yyyy-MM-dd HH:mm:ss').format(date);
}
}
LogDataRepository
import 'package:fiona_logger/src/domain/log_data.dart';
abstract class LogDataRepository {
Future<List<LogData>> getAll();
Future<void> add(LogData logData);
Future<void> clear();
}
FionaLogger
import 'package:fiona_logger/src/domain/log_data.dart';
import 'package:fiona_logger/src/domain/log_data_repository.dart';
import 'package:get_it/get_it.dart';
enum Level { verbose, debug, info, warning, error }
class FionaLogger {
Level level;
late LogDataRepository logDataRepository;
FionaLogger({this.level = Level.verbose}) {
logDataRepository = GetIt.instance<LogDataRepository>();
}
Future<List<LogData>> getAll() async {
return logDataRepository.getAll();
}
Future<void> clear() async {
logDataRepository.clear();
}
void log(LogData log, Level level) {
int levelIndex = Level.values.indexOf(level);
int logLevelIndex = Level.values.indexOf(this.level);
if (logLevelIndex <= levelIndex) {
_writeLog(log);
}
}
void _writeLog(LogData log) {
logDataRepository.add(log);
}
void v(String message) {
LogData data = LogData(message, level: Level.verbose.toString());
data.datetime = DateTime.now().toUtc().millisecondsSinceEpoch;
log(data, Level.verbose);
}
void w(String message) {
LogData data = LogData(message, level: Level.warning.toString());
data.datetime = DateTime.now().toUtc().millisecondsSinceEpoch;
log(data, Level.warning);
}
void i(String message) {
LogData data = LogData(message, level: Level.info.toString());
data.datetime = DateTime.now().toUtc().millisecondsSinceEpoch;
log(data, Level.info);
}
void d(String message) {
LogData data = LogData(message, level: Level.debug.toString());
data.datetime = DateTime.now().toUtc().millisecondsSinceEpoch;
log(data, Level.debug);
}
void e(String message) {
LogData data = LogData(message, level: Level.error.toString());
data.datetime = DateTime.now().toUtc().millisecondsSinceEpoch;
log(data, Level.error);
}
}
The package includes LogInConsole repository and as you can imagine, it sends the message to the console.
LogInConsole
import 'package:fiona_logger/src/domain/log_data.dart';
import 'package:fiona_logger/src/domain/log_data_repository.dart';
import 'package:flutter/foundation.dart';
class LogInConsole implements LogDataRepository {
List<LogData> logs = List.empty(growable: true);
@override
Future<void> clear() async {
logs = List.empty(growable: true);
}
@override
Future<void> add(LogData logData) async {
logs.add(logData);
debugPrint(logData.message);
}
@override
Future<List<LogData>> getAll() async {
return logs;
}
}
Then you will be able to write LogInFile, LogInDatabase, LogToView, whatever you want, whitout limitations.
Use the data repository you want
As you can see FionaLogger uses GetIt to get the instance of the LogDataRepository.
By default, you can use LogInConsole:
GetIt.instance.registerLazySingleton<LogDataRepository>(() => LogInConsole());
But you will be able to set the LogDataRepository you want to create.
Log to SQLite
For example, I have a project where I use this package and I created a LogDataRepository to use SQLite:
class LogDataSQLite implements LogDataRepository{
SQLiteDatabase _sqlite = DependencyManager().get<SQLiteDatabase>();
@override
Future<void> clear() async {
final Database db = await _sqlite.initializeDB();
try {
await db.delete(SQLiteScheme.logData_tableName);
} catch (err) {
}
}
@override
Future<void> add(LogData logData) async {
final Database db = await _sqlite.initializeDB();
await db.insert(
SQLiteScheme.logData_tableName, logData.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
@override
Future<List<LogData>> getAll() async {
final Database db = await _sqlite.initializeDB();
final List<Map<String, Object?>> queryResult =
await db.query(SQLiteScheme.logData_tableName, orderBy: 'datetime DESC');
return queryResult.map((e) => LogData.fromMap(e)).toList();
}
Then into my project startup settings , I inject that dependency for the data repository:
GetIt.instance.registerLazySingleton<LogDataRepository>(() => LogDataSQLite());
Conclusion
- Every project needs a Logger.
- The console log is useful when you are testing with simulators.
- The sqlite log is awesome when you are using real device for testing or production. The user will be able to send you the log by email or by the way you define using the device sqlite database.
Thanks for reading, Clap if you like it!
Let me know your comments below.