LCOV - code coverage report
Current view: top level - src - the_logger.dart Coverage Total Hit
Test: filtered_lcov.info Lines: 91.3 % 92 84
Test Date: 2026-04-08 10:00:59 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : 
       3              : import 'package:collection/collection.dart';
       4              : import 'package:flutter/foundation.dart';
       5              : import 'package:logging/logging.dart';
       6              : import 'package:the_logger/src/abstract_logger.dart';
       7              : import 'package:the_logger/src/console_logger.dart';
       8              : import 'package:the_logger/src/db/logger_database.dart';
       9              : import 'package:the_logger/src/db_logger.dart';
      10              : import 'package:the_logger/src/models/models.dart';
      11              : 
      12              : export 'abstract_logger.dart';
      13              : export 'models/console_colors.dart';
      14              : export 'models/masked_log_record.dart';
      15              : export 'models/masking_string.dart';
      16              : 
      17              : /// The Logger: modularity, extensibility, testability
      18              : class TheLogger {
      19              :   /// Get logger instance
      20            5 :   factory TheLogger.i() {
      21            5 :     _instance ??= TheLogger._();
      22              :     return _instance!;
      23              :   }
      24            5 :   TheLogger._();
      25              : 
      26              :   static TheLogger? _instance;
      27              : 
      28              :   final _log = Logger('TheLogger');
      29            0 :   late List<AbstractLogger> _loggers = [];
      30              :   Level _minLevel = Level.ALL;
      31              :   String? _sessionStartExtra;
      32              :   late Level _sessionStartLevel;
      33              : 
      34              :   final MaskingStrings _maskingStrings = {};
      35              : 
      36              :   bool _initialized = false;
      37              : 
      38              :   final StreamController<MaskedLogRecord> _streamController =
      39              :       StreamController<MaskedLogRecord>.broadcast();
      40              : 
      41              :   /// A broadcast stream of masked log records.
      42              :   ///
      43              :   /// Use this to monitor logs in real-time. Supports multiple listeners.
      44              :   /// Can be filtered with standard [Stream.where]:
      45              :   /// ```dart
      46              :   /// TheLogger.i().stream
      47              :   ///     .where((r) => r.level >= Level.WARNING)
      48              :   ///     .listen((record) => print(record.message));
      49              :   /// ```
      50            3 :   Stream<MaskedLogRecord> get stream => _streamController.stream;
      51              : 
      52              :   /// Init app logger
      53              :   ///
      54              :   /// [retainStrategy] processing algorythm:
      55              :   /// * sort all records by level (ALL->OFF)
      56              :   /// * record with minimum level will be used as global filter
      57              :   ///   (for storing and printing)
      58              :   /// * each integer for a level means how many sessions the records with this
      59              :   ///   level will be retained
      60              :   /// * each next entry will add this number
      61              :   /// * if [retainStrategy] is empty => {Level.ALL: 10}
      62              :   /// So, examples:
      63              :   ///
      64              :   /// {
      65              :   ///   Level.ALL:      200,  // ALL records will be deleted after 200 sessions
      66              :   ///   Level.INFO:     100,  // records with INFO and higher level retained for 300 sessions
      67              :   ///   Level.SEVERE:   50,   // records with SEVERE and higher level retained for 350 sessions
      68              :   /// }
      69              :   ///
      70              :   /// {
      71              :   ///   Level.CONFIG:   200,  // records with CONFIG and higher level retained for 200 sessions
      72              :   ///                         // lower level records (FINE, FINER and FINEST) will not
      73              :   ///                         // be printed nor stored because lowest level in the map
      74              :   ///                         // is CONFIG
      75              :   ///   Level.INFO:     100,  // records with INFO and higher level retained for 300 sessions
      76              :   ///   Level.SEVERE:   50,   // records with SEVERE and higher level retained for 350 sessions
      77              :   /// }
      78              :   ///
      79              :   /// {
      80              :   ///   Level.OFF:   0,       // disable logging
      81              :   /// }
      82              :   ///
      83              :   /// {
      84              :   ///   Level.ALL:   1,       // all level records will be retained for 1 session
      85              :   ///                         // (i.e. you will be able to retrieve the logs from the
      86              :   ///                         // previous run)
      87              :   /// }
      88              :   /// [startNewSession] - if true, new session will be started
      89              :   /// [consoleLogger] - if true, console logger will be used
      90              :   /// [dbLogger] - if true, db logger will be used
      91              :   /// [maskDbLogger] - mask sensitive data in db logger
      92              :   /// [consoleLoggerCallback] - callback for console logger
      93              :   /// [consoleColors] - console colors
      94              :   /// [consoleFormatJson] - format JSON in console logger
      95              :   /// [sessionStartExtra] - extra info for session start, will be added to all
      96              :   /// session start records
      97              :   /// [customLoggers] - custom loggers
      98              :   /// [sessionStartLevel] - session start log level
      99            5 :   Future<void> init({
     100              :     Map<Level, int> retainStrategy = const {},
     101              :     bool startNewSession = true,
     102              :     bool consoleLogger = true,
     103              :     bool dbLogger = true,
     104              :     bool maskDbLogger = true,
     105              :     ConsoleLoggerCallback? consoleLoggerCallback,
     106              :     ConsoleColors consoleColors = const ConsoleColors(),
     107              :     bool consoleFormatJson = true,
     108              :     String? sessionStartExtra,
     109              :     List<AbstractLogger>? customLoggers,
     110              :     Level sessionStartLevel = Level.INFO,
     111              :     @visibleForTesting LoggerDatabase? database,
     112              :   }) async {
     113            5 :     if (_initialized) {
     114            0 :       _log.warning('TheLogger is already initialized!');
     115              :       return;
     116              :     }
     117              : 
     118            5 :     _initialized = true;
     119              : 
     120            5 :     _sessionStartExtra = sessionStartExtra;
     121            5 :     _sessionStartLevel = sessionStartLevel;
     122              : 
     123              :     // If there are no explicit instructions on how to retain logs
     124            5 :     final retainStrategyNotEmpty = retainStrategy.isEmpty
     125            5 :         ? _defaultRetainStrategy()
     126              :         : retainStrategy;
     127              : 
     128           15 :     _minLevel = retainStrategyNotEmpty.keys.reduce(
     129            3 :       (value, element) => element.compareTo(value) < 0 ? element : value,
     130              :     );
     131              : 
     132           15 :     Logger.root.level = _minLevel;
     133           10 :     _loggers = <AbstractLogger>[
     134              :       if (consoleLogger)
     135            4 :         ConsoleLogger(
     136              :           loggerCallback: consoleLoggerCallback,
     137              :           colors: consoleColors,
     138              :           formatJson: consoleFormatJson,
     139              :         ),
     140            2 :       if (dbLogger) DbLogger(),
     141            9 :       ...customLoggers ?? [],
     142              :     ];
     143              : 
     144           10 :     for (final logger in _loggers) {
     145            5 :       if (logger is DbLogger) {
     146            2 :         await logger.init(retainStrategyNotEmpty, database);
     147            2 :         logger.shouldMask = maskDbLogger;
     148              :       } else {
     149            5 :         await logger.init(retainStrategyNotEmpty);
     150              :       }
     151              :     }
     152              : 
     153           10 :     Logger.root.clearListeners();
     154           20 :     Logger.root.onRecord.listen(_writeRecord);
     155              : 
     156            1 :     if (startNewSession) await startSession();
     157              :   }
     158              : 
     159              :   /// Dispose logger
     160            5 :   Future<void> dispose() async {
     161            5 :     _assureInitialized();
     162              : 
     163           10 :     Logger.root.clearListeners();
     164           10 :     await _streamController.close();
     165           10 :     for (final logger in _loggers) {
     166            5 :       await logger.dispose();
     167              :     }
     168              :     _instance = null;
     169              :   }
     170              : 
     171              :   /// Get computed minimal level
     172            0 :   Level get minLevel => _minLevel;
     173              : 
     174            5 :   void _writeRecord(LogRecord record) {
     175            5 :     final maskedRecord = MaskedLogRecord.fromLogRecord(
     176              :       record,
     177            5 :       maskingStrings: _maskingStrings,
     178              :     );
     179           10 :     for (final logger in _loggers) {
     180            5 :       logger.write(maskedRecord);
     181              :     }
     182           10 :     if (!_streamController.isClosed) {
     183           10 :       _streamController.add(maskedRecord);
     184              :     }
     185              :   }
     186              : 
     187              :   /// Increment session id
     188            4 :   Future<void> startSession() async {
     189            4 :     _assureInitialized();
     190              : 
     191            4 :     final logStrings = <String>[];
     192            8 :     for (final logger in _loggers) {
     193            4 :       final logString = await logger.sessionStart();
     194              :       if (logString != null) {
     195            3 :         logStrings.add(logString);
     196              :       }
     197              :     }
     198              : 
     199            4 :     final logStringsReduced = logStrings.fold(
     200              :       '',
     201            6 :       (previousValue, element) => '$previousValue$element',
     202              :     );
     203              : 
     204            4 :     final extraString = _sessionStartExtra == null
     205              :         ? ''
     206            4 :         : ' $_sessionStartExtra';
     207            8 :     _log.log(
     208            4 :       _sessionStartLevel,
     209            4 :       'Session start $logStringsReduced$extraString',
     210              :     );
     211              :   }
     212              : 
     213              :   /// Get all logs as strings (for debug purposes only)
     214            1 :   Future<String> getAllLogsAsString() async {
     215            1 :     _assureInitialized();
     216              : 
     217            2 :     return _dbLogger.getAllLogsAsString();
     218              :   }
     219              : 
     220              :   /// Get all logs as [LogRecord]s (for debug purposes only)
     221            0 :   @visibleForTesting
     222              :   Future<List<LogRecord>> getAllLogs() async {
     223            0 :     _assureInitialized();
     224              : 
     225            0 :     return _dbLogger.getAllLogs();
     226              :   }
     227              : 
     228              :   /// Get all logs as maps (for debug purposes only)
     229            2 :   @visibleForTesting
     230              :   Future<List<Map<String, Object?>>> getAllLogsAsMaps() async {
     231            2 :     _assureInitialized();
     232              : 
     233            4 :     return _dbLogger.getAllLogsAsMaps();
     234              :   }
     235              : 
     236              :   /// Write logs to archived JSON, return file path
     237            1 :   Future<String> writeAllLogsToJson([String filename = 'logs.json']) async {
     238            1 :     _assureInitialized();
     239              : 
     240            2 :     return _dbLogger.writeAllLogsToJson(filename);
     241              :   }
     242              : 
     243              :   /// Clear logs (for debug purposes only)
     244            2 :   Future<void> clearAllLogs() {
     245            2 :     _assureInitialized();
     246              : 
     247            4 :     return _dbLogger.clearAllLogs();
     248              :   }
     249              : 
     250              :   /// Add masking string
     251            3 :   void addMaskingString(MaskingString maskingString) {
     252            3 :     _assureInitialized();
     253              : 
     254            6 :     addMaskingStrings({maskingString});
     255              :   }
     256              : 
     257              :   /// Add masking strings
     258            3 :   void addMaskingStrings(MaskingStrings maskingStrings) {
     259            3 :     _assureInitialized();
     260              : 
     261            6 :     _maskingStrings.addAll(maskingStrings);
     262              :   }
     263              : 
     264              :   /// Remove masking string
     265            1 :   void removeMaskingString(MaskingString maskingString) {
     266            1 :     _assureInitialized();
     267              : 
     268            2 :     removeMaskingStrings({maskingString});
     269              :   }
     270              : 
     271              :   /// Remove masking strings
     272            1 :   void removeMaskingStrings(MaskingStrings maskingStrings) {
     273            1 :     _assureInitialized();
     274              : 
     275            2 :     _maskingStrings.removeAll(maskingStrings);
     276              :   }
     277              : 
     278              :   /// Clear masking strings
     279            1 :   void clearMaskingStrings() {
     280            1 :     _assureInitialized();
     281              : 
     282            2 :     _maskingStrings.clear();
     283              :   }
     284              : 
     285            2 :   DbLogger get _dbLogger {
     286              :     final dbLogger =
     287            8 :         _loggers.firstWhereOrNull((logger) => logger is DbLogger) as DbLogger?;
     288              :     if (dbLogger == null) {
     289            0 :       throw Exception('DbLogger is not initialized!');
     290              :     }
     291              : 
     292              :     return dbLogger;
     293              :   }
     294              : 
     295              :   /// Default retain strategy
     296           10 :   Map<Level, int> _defaultRetainStrategy() => {Level.ALL: 10};
     297              : 
     298            5 :   void _assureInitialized() {
     299            5 :     if (!_initialized) {
     300            0 :       throw Exception('TheLogger is not initialized!');
     301              :     }
     302              :   }
     303              : }
        

Generated by: LCOV version 2.4-0