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 = []; static const _maxBufferSize = 50; LogService._({required LogConfig config}) : _config = config; static Future 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? 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, Map? 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, Map? 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? 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(), errorMessage: error.toString(), stackTrace: stackTrace.toString(), extra: extra, ), ); } void flush() { _flushBuffer(); _fileHandler?.flush(); } Future> readLogs() async { return await _fileHandler?.readAllLines() ?? []; } }