feat(logging): add logging module skeleton
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import 'log_entry.dart';
|
||||
|
||||
enum LogOutput { console, file }
|
||||
|
||||
class LogConfig {
|
||||
final LogLevel minLevel;
|
||||
final LogOutput output;
|
||||
final String logFileName;
|
||||
final String logDir;
|
||||
|
||||
const LogConfig({
|
||||
this.minLevel = LogLevel.debug,
|
||||
this.output = LogOutput.console,
|
||||
this.logFileName = 'app.log',
|
||||
this.logDir = 'logs',
|
||||
});
|
||||
|
||||
static LogConfig forDebug() =>
|
||||
const LogConfig(minLevel: LogLevel.debug, output: LogOutput.console);
|
||||
|
||||
static LogConfig forRelease() => const LogConfig(
|
||||
minLevel: LogLevel.warning,
|
||||
output: LogOutput.file,
|
||||
logFileName: 'app.log',
|
||||
logDir: 'logs',
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
enum LogLevel { debug, info, warning, error }
|
||||
|
||||
class LogEntry {
|
||||
final DateTime timestamp;
|
||||
final LogLevel level;
|
||||
final String message;
|
||||
final String module;
|
||||
final String? funcName;
|
||||
final int? lineNo;
|
||||
final String? errorType;
|
||||
final String? stackTrace;
|
||||
final Map<String, dynamic>? extra;
|
||||
|
||||
LogEntry({
|
||||
required this.timestamp,
|
||||
required this.level,
|
||||
required this.message,
|
||||
required this.module,
|
||||
this.funcName,
|
||||
this.lineNo,
|
||||
this.errorType,
|
||||
this.stackTrace,
|
||||
this.extra,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'level': level.name,
|
||||
'message': message,
|
||||
'module': module,
|
||||
if (funcName != null) 'func_name': funcName,
|
||||
if (lineNo != null) 'line_no': lineNo,
|
||||
if (errorType != null) 'error_type': errorType,
|
||||
if (stackTrace != null) 'stack_trace': stackTrace,
|
||||
if (extra != null && extra!.isNotEmpty) 'extra': extra,
|
||||
};
|
||||
|
||||
String toConsoleString() {
|
||||
final ts = timestamp.toIso8601String();
|
||||
final location = [
|
||||
if (funcName != null) funcName,
|
||||
if (lineNo != null) '@$lineNo',
|
||||
].join('');
|
||||
final locationStr = location.isNotEmpty ? ' [$location]' : '';
|
||||
final errorStr = errorType != null ? ' [$errorType]' : '';
|
||||
final extraStr = extra != null && extra!.isNotEmpty ? ' $extra' : '';
|
||||
return '$ts ${level.name.toUpperCase().padRight(7)} [$module$locationStr]$errorStr $message$extraStr';
|
||||
}
|
||||
|
||||
String toFileString() {
|
||||
final sb = StringBuffer();
|
||||
sb.writeln('[$timestamp] ${level.name.toUpperCase()} [$module]');
|
||||
if (funcName != null || lineNo != null) {
|
||||
sb.write(' at $funcName' ?? '');
|
||||
if (lineNo != null) sb.write(':$lineNo');
|
||||
sb.writeln();
|
||||
}
|
||||
sb.writeln(' $message');
|
||||
if (errorType != null) {
|
||||
sb.writeln(' Error: $errorType');
|
||||
}
|
||||
if (stackTrace != null) {
|
||||
sb.writeln(' StackTrace:');
|
||||
sb.writeln(stackTrace);
|
||||
}
|
||||
if (extra != null && extra!.isNotEmpty) {
|
||||
sb.writeln(' Extra: $extra');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class LogFileHandler {
|
||||
File? _file;
|
||||
IOSink? _sink;
|
||||
|
||||
Future<void> init(String logDir, String logFileName) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final logPath = '${dir.path}/$logDir';
|
||||
await Directory(logPath).create(recursive: true);
|
||||
_file = File('$logPath/$logFileName');
|
||||
_sink = _file!.openWrite(mode: FileMode.append);
|
||||
}
|
||||
|
||||
void write(String content) {
|
||||
_sink?.writeln(content);
|
||||
}
|
||||
|
||||
Future<void> flush() async {
|
||||
await _sink?.flush();
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
await _sink?.close();
|
||||
_sink = null;
|
||||
_file = null;
|
||||
}
|
||||
|
||||
Future<List<String>> readAllLines() async {
|
||||
if (_file == null || !await _file!.exists()) return [];
|
||||
return await _file!.readAsLines();
|
||||
}
|
||||
|
||||
String? get filePath => _file?.path;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'log_config.dart';
|
||||
import 'log_entry.dart';
|
||||
import 'log_file_handler.dart';
|
||||
|
||||
class LogService {
|
||||
final LogConfig _config;
|
||||
LogFileHandler? _fileHandler;
|
||||
final _buffer = <String>[];
|
||||
static const _maxBufferSize = 50;
|
||||
|
||||
LogService._({required LogConfig config}) : _config = config;
|
||||
|
||||
static Future<LogService> create({LogConfig? config}) async {
|
||||
final isRelease = kReleaseMode;
|
||||
|
||||
final effectiveConfig =
|
||||
config ?? (isRelease ? LogConfig.forRelease() : LogConfig.forDebug());
|
||||
|
||||
final service = LogService._(config: effectiveConfig);
|
||||
|
||||
if (effectiveConfig.output == LogOutput.file) {
|
||||
service._fileHandler = LogFileHandler();
|
||||
await service._fileHandler!.init(
|
||||
effectiveConfig.logDir,
|
||||
effectiveConfig.logFileName,
|
||||
);
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
String? get logFilePath => _fileHandler?.filePath;
|
||||
|
||||
void _log(LogEntry entry) {
|
||||
if (entry.level.index < _config.minLevel.index) return;
|
||||
|
||||
if (_config.output == LogOutput.console) {
|
||||
debugPrint(entry.toConsoleString());
|
||||
if (entry.stackTrace != null) {
|
||||
debugPrint(entry.stackTrace!);
|
||||
}
|
||||
} else {
|
||||
_buffer.add(entry.toFileString());
|
||||
if (_buffer.length >= _maxBufferSize) {
|
||||
_flushBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _flushBuffer() {
|
||||
for (final line in _buffer) {
|
||||
_fileHandler?.write(line);
|
||||
}
|
||||
_buffer.clear();
|
||||
_fileHandler?.flush();
|
||||
}
|
||||
|
||||
(String?, int?) _extractLocation(StackTrace stackTrace) {
|
||||
final frames = stackTrace.toString().split('\n');
|
||||
for (final frame in frames) {
|
||||
if (frame.contains('.dart')) {
|
||||
final match = RegExp(
|
||||
r'#\d+\s+(.+?)\s+\((.+?):(\d+)\)',
|
||||
).firstMatch(frame);
|
||||
if (match != null) {
|
||||
return (match.group(1), int.tryParse(match.group(3) ?? ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
void debug({
|
||||
required String message,
|
||||
required String module,
|
||||
Map<String, dynamic>? extra,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final trace = stackTrace ?? StackTrace.current;
|
||||
final (funcName, lineNo) = _extractLocation(trace);
|
||||
_log(
|
||||
LogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
level: LogLevel.debug,
|
||||
message: message,
|
||||
module: module,
|
||||
funcName: funcName,
|
||||
lineNo: lineNo,
|
||||
extra: extra,
|
||||
stackTrace: trace.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void info({
|
||||
required String message,
|
||||
required String module,
|
||||
required Map<String, dynamic> extra,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final trace = stackTrace ?? StackTrace.current;
|
||||
final (funcName, lineNo) = _extractLocation(trace);
|
||||
_log(
|
||||
LogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
level: LogLevel.info,
|
||||
message: message,
|
||||
module: module,
|
||||
funcName: funcName,
|
||||
lineNo: lineNo,
|
||||
extra: extra,
|
||||
stackTrace: trace.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void warning({
|
||||
required String message,
|
||||
required String module,
|
||||
required Map<String, dynamic> extra,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final trace = stackTrace ?? StackTrace.current;
|
||||
final (funcName, lineNo) = _extractLocation(trace);
|
||||
_log(
|
||||
LogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
level: LogLevel.warning,
|
||||
message: message,
|
||||
module: module,
|
||||
funcName: funcName,
|
||||
lineNo: lineNo,
|
||||
extra: extra,
|
||||
stackTrace: trace.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void error({
|
||||
required String message,
|
||||
required Object error,
|
||||
required StackTrace stackTrace,
|
||||
required String module,
|
||||
Map<String, dynamic>? extra,
|
||||
}) {
|
||||
final (funcName, lineNo) = _extractLocation(stackTrace);
|
||||
_log(
|
||||
LogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
level: LogLevel.error,
|
||||
message: message,
|
||||
module: module,
|
||||
funcName: funcName,
|
||||
lineNo: lineNo,
|
||||
errorType: error.runtimeType.toString(),
|
||||
stackTrace: stackTrace.toString(),
|
||||
extra: extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
_flushBuffer();
|
||||
_fileHandler?.flush();
|
||||
}
|
||||
|
||||
Future<List<String>> readLogs() async {
|
||||
return await _fileHandler?.readAllLines() ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'log_service.dart';
|
||||
|
||||
LogService? _globalLogService;
|
||||
|
||||
void setLogService(LogService service) {
|
||||
_globalLogService = service;
|
||||
}
|
||||
|
||||
LogService _ensureService() {
|
||||
return _globalLogService ?? (throw StateError('LogService not initialized'));
|
||||
}
|
||||
|
||||
class Logger {
|
||||
final String module;
|
||||
final LogService _service;
|
||||
|
||||
Logger(this.module, this._service);
|
||||
|
||||
factory Logger.get(String module) {
|
||||
return Logger(module, _ensureService());
|
||||
}
|
||||
|
||||
void debug({
|
||||
required String message,
|
||||
Map<String, dynamic>? extra,
|
||||
StackTrace? stackTrace,
|
||||
}) => _service.debug(
|
||||
message: message,
|
||||
module: module,
|
||||
extra: extra ?? {},
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
void info({
|
||||
required String message,
|
||||
required Map<String, dynamic> extra,
|
||||
StackTrace? stackTrace,
|
||||
}) => _service.info(
|
||||
message: message,
|
||||
module: module,
|
||||
extra: extra,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
void warning({
|
||||
required String message,
|
||||
required Map<String, dynamic> extra,
|
||||
StackTrace? stackTrace,
|
||||
}) => _service.warning(
|
||||
message: message,
|
||||
module: module,
|
||||
extra: extra,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
void error({
|
||||
required String message,
|
||||
required Object error,
|
||||
required StackTrace stackTrace,
|
||||
Map<String, dynamic>? extra,
|
||||
}) => _service.error(
|
||||
message: message,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
module: module,
|
||||
extra: extra,
|
||||
);
|
||||
}
|
||||
|
||||
Logger getLogger(String module) => Logger.get(module);
|
||||
Reference in New Issue
Block a user