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