LCOV - code coverage report
Current view: top level - src - db_logger.dart Coverage Total Hit
Test: filtered_lcov.info Lines: 90.9 % 88 80
Test Date: 2026-04-08 10:00:59 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : 
       3              : import 'package:drift/drift.dart';
       4              : import 'package:flutter/foundation.dart';
       5              : import 'package:flutter/widgets.dart';
       6              : import 'package:logging/logging.dart';
       7              : import 'package:the_logger/src/abstract_logger.dart';
       8              : import 'package:the_logger/src/db/logger_database.dart';
       9              : import 'package:the_logger/src/log_export.dart';
      10              : import 'package:the_logger/src/models/models.dart';
      11              : 
      12              : /// Database logger
      13              : class DbLogger extends AbstractLogger {
      14              :   late LoggerDatabase _database;
      15              :   late final Map<Level, int> _retainStrategy;
      16              :   int _sessionId = -1;
      17              :   final List<Future<void>> _pendingOperations = [];
      18              : 
      19            2 :   @override
      20              :   Future<void> init(
      21              :     Map<Level, int> retainStrategy, [
      22              :     @visibleForTesting LoggerDatabase? database,
      23              :   ]) async {
      24            2 :     WidgetsFlutterBinding.ensureInitialized();
      25              : 
      26            2 :     _retainStrategy = retainStrategy;
      27              : 
      28            2 :     _database = database ?? LoggerDatabase();
      29              :   }
      30              : 
      31            2 :   @override
      32              :   Future<String?> sessionStart() async {
      33            4 :     _sessionId = await _database
      34            6 :         .into(_database.sessions)
      35            4 :         .insert(SessionsCompanion.insert());
      36              : 
      37            2 :     final cleanupFuture = Future.delayed(
      38              :       const Duration(milliseconds: 200),
      39            2 :       _cleanup,
      40              :     );
      41            4 :     _pendingOperations.add(cleanupFuture);
      42            2 :     unawaited(
      43            2 :       cleanupFuture.whenComplete(
      44            6 :         () => _pendingOperations.remove(cleanupFuture),
      45              :       ),
      46              :     );
      47              : 
      48            4 :     return 'new session id: $_sessionId';
      49              :   }
      50              : 
      51            2 :   @override
      52              :   void write(MaskedLogRecord record) {
      53            2 :     unawaited(
      54            2 :       _database
      55            6 :           .into(_database.records)
      56            2 :           .insert(
      57            2 :             RecordsCompanion.insert(
      58            4 :               sessionId: Value(_sessionId),
      59            6 :               level: Value(record.level.value),
      60            2 :               message: Value(
      61            5 :                 shouldMask ? record.maskedMessage : record.message,
      62              :               ),
      63            4 :               loggerName: Value(record.loggerName),
      64            2 :               error: Value(
      65            5 :                 shouldMask ? record.maskedError : record.error?.toString(),
      66              :               ),
      67            2 :               stackTrace: Value(
      68            2 :                 shouldMask
      69            2 :                     ? record.maskedStackTrace
      70            2 :                     : record.stackTrace.toString(),
      71              :               ),
      72            6 :               time: Value(record.time.microsecondsSinceEpoch),
      73              :             ),
      74              :           ),
      75              :     );
      76              :   }
      77              : 
      78              :   /// Get all logs as strings
      79            1 :   Future<String> getAllLogsAsString() async {
      80              :     final list =
      81            6 :         await (_database.select(_database.records)..orderBy([
      82            3 :               (t) => OrderingTerm.asc(t.recordTimestamp),
      83              :             ]))
      84            1 :             .get();
      85              : 
      86            2 :     return list.map((element) => '$element').join('\n');
      87              :   }
      88              : 
      89              :   /// Get all logs as [LogRecord]s (for debug purposes only)
      90            0 :   Future<List<LogRecord>> getAllLogs() async {
      91            0 :     final list = await _database
      92            0 :         .customSelect(
      93              :           'SELECT * FROM records ORDER BY record_timestamp ASC',
      94              :         )
      95            0 :         .get();
      96              : 
      97              :     return list
      98            0 :         .map((row) => WritableLogRecord.fromMap(row.data.cast()))
      99            0 :         .toList();
     100              :   }
     101              : 
     102              :   /// Get all logs as maps (for debug purposes only)
     103            2 :   Future<List<Map<String, Object?>>> getAllLogsAsMaps() async {
     104            2 :     final list = await _database
     105            2 :         .customSelect(
     106              :           'SELECT * FROM records ORDER BY record_timestamp ASC',
     107              :         )
     108            2 :         .get();
     109              : 
     110           10 :     return list.map((row) => row.data.cast<String, Object?>()).toList();
     111              :   }
     112              : 
     113              :   /// Write logs to archived JSON, return file path
     114            1 :   Future<String> writeAllLogsToJson(String filename) async {
     115              :     if (kIsWeb) {
     116            0 :       throw UnsupportedError('Log export is not supported on web');
     117              :     }
     118            2 :     return writeLogsToFile(_database, filename);
     119              :   }
     120              : 
     121            2 :   Future<void> _cleanup() async {
     122            8 :     if (_sessionId < 0 || _retainStrategy.isEmpty) return;
     123              : 
     124            6 :     final levelList = _retainStrategy.entries.toList()
     125            6 :       ..sort((a, b) => b.key.compareTo(a.key));
     126              : 
     127              :     Level? nextLevel;
     128            2 :     var retain = _sessionId;
     129              :     final leveListWithBouds = levelList
     130            2 :         .map(
     131            2 :           (e) {
     132            2 :             final ret = _LevelBound(
     133            2 :               level: e.key,
     134              :               nextLevel: nextLevel,
     135            2 :               sessionId: e.value,
     136              :             );
     137            2 :             nextLevel = e.key;
     138              : 
     139              :             return ret;
     140              :           },
     141              :         )
     142            2 :         .toList()
     143            2 :         .reversed
     144            4 :         .map((e) {
     145            4 :           retain -= e.sessionId;
     146              : 
     147            2 :           return e.copyWith(sessionId: retain);
     148              :         })
     149            2 :         .toList();
     150              : 
     151            4 :     final where = leveListWithBouds.fold('', (previousValue, element) {
     152            3 :       final previous = previousValue.isNotEmpty ? '$previousValue OR ' : '';
     153            2 :       final next = element.nextLevel == null
     154              :           ? ''
     155            3 :           : 'AND level < ${element.nextLevel!.value}';
     156              : 
     157              :       return '''
     158            6 :         $previous(session_id < ${element.sessionId} AND level >= ${element.level.value} $next)
     159            2 :         ''';
     160              :     });
     161              : 
     162              :     final query =
     163              :         '''
     164              :       DELETE FROM records WHERE $where;
     165            2 :     ''';
     166              : 
     167            4 :     await _database.customStatement(query);
     168              :   }
     169              : 
     170              :   /// Clear logs (for debug purposes only)
     171            2 :   Future<void> clearAllLogs() async {
     172           10 :     await _database.delete(_database.records).go();
     173              :   }
     174              : 
     175              :   /// Whether to mask logs
     176              :   bool shouldMask = true;
     177              : 
     178            2 :   @override
     179              :   Future<void> dispose() async {
     180            4 :     await Future.wait(_pendingOperations);
     181            4 :     _pendingOperations.clear();
     182            4 :     await _database.close();
     183              :   }
     184              : }
     185              : 
     186              : class _LevelBound {
     187            2 :   _LevelBound({
     188              :     required this.level,
     189              :     required this.nextLevel,
     190              :     required this.sessionId,
     191              :   });
     192              :   final Level level;
     193              :   final Level? nextLevel;
     194              :   final int sessionId;
     195              : 
     196            4 :   _LevelBound copyWith({int? sessionId}) => _LevelBound(
     197            2 :     level: level,
     198            2 :     nextLevel: nextLevel,
     199            0 :     sessionId: sessionId ?? this.sessionId,
     200              :   );
     201              : }
        

Generated by: LCOV version 2.4-0