diff --git a/lib/logger.dart b/lib/logger.dart index 7571068..71782de 100644 --- a/lib/logger.dart +++ b/lib/logger.dart @@ -1,8 +1,27 @@ /// Small, easy to use and extensible logger which prints beautiful logs. library logger; -export 'src/outputs/file_output_stub.dart' - if (dart.library.io) 'src/outputs/file_output.dart'; +export 'src/ansi_color.dart'; +export 'src/date_time_format.dart'; +export 'src/filters/development_filter.dart'; +export 'src/filters/production_filter.dart'; +export 'src/log_event.dart'; +export 'src/log_filter.dart'; +export 'src/log_level.dart'; +export 'src/log_output.dart'; +export 'src/log_printer.dart'; +export 'src/logger.dart'; +export 'src/output_event.dart'; export 'src/outputs/advanced_file_output_stub.dart' if (dart.library.io) 'src/outputs/advanced_file_output.dart'; -export 'web.dart'; +export 'src/outputs/console_output.dart'; +export 'src/outputs/file_output_stub.dart' + if (dart.library.io) 'src/outputs/file_output.dart'; +export 'src/outputs/memory_output.dart'; +export 'src/outputs/multi_output.dart'; +export 'src/outputs/stream_output.dart'; +export 'src/printers/hybrid_printer.dart'; +export 'src/printers/logfmt_printer.dart'; +export 'src/printers/prefix_printer.dart'; +export 'src/printers/pretty_printer.dart'; +export 'src/printers/simple_printer.dart'; diff --git a/lib/src/date_time_format.dart b/lib/src/date_time_format.dart index d93b5a3..39af99d 100644 --- a/lib/src/date_time_format.dart +++ b/lib/src/date_time_format.dart @@ -30,6 +30,12 @@ class DateTimeFormat { /// * `2019-06-04 12:30:40.550` static const DateTimeFormatter dateAndTime = _dateAndTime; + /// Prints date and time in ISO-8601 format. + /// + /// See also: + /// [DateTime.toIso8601String] + static const DateTimeFormatter iso8601 = _iso8601; + DateTimeFormat._(); static String _none(DateTime t) => throw UnimplementedError(); @@ -67,4 +73,8 @@ class DateTimeFormat { static String _dateAndTime(DateTime t) { return "${_onlyDate(t)} ${_onlyTime(t)}"; } + + static String _iso8601(DateTime t) { + return t.toIso8601String(); + } } diff --git a/lib/src/filters/development_filter.dart b/lib/src/filters/development_filter.dart index a2414e2..c4a7348 100644 --- a/lib/src/filters/development_filter.dart +++ b/lib/src/filters/development_filter.dart @@ -7,14 +7,14 @@ import '../log_filter.dart'; /// In release mode ALL logs are omitted. class DevelopmentFilter extends LogFilter { @override - bool shouldLog(LogEvent event) { + FilterResult shouldLog(LogEvent event) { var shouldLog = false; assert(() { - if (event.level >= level!) { + if (event.level >= level) { shouldLog = true; } return true; }()); - return shouldLog; + return shouldLog ? FilterResult.accept : FilterResult.deny; } } diff --git a/lib/src/filters/production_filter.dart b/lib/src/filters/production_filter.dart index 533b0b2..21ffd66 100644 --- a/lib/src/filters/production_filter.dart +++ b/lib/src/filters/production_filter.dart @@ -4,7 +4,7 @@ import '../log_filter.dart'; /// Prints all logs with `level >= Logger.level` even in production. class ProductionFilter extends LogFilter { @override - bool shouldLog(LogEvent event) { - return event.level >= level!; + FilterResult shouldLog(LogEvent event) { + return event.level >= level ? FilterResult.accept : FilterResult.deny; } } diff --git a/lib/src/log_event.dart b/lib/src/log_event.dart index 38067d0..f4f0847 100644 --- a/lib/src/log_event.dart +++ b/lib/src/log_event.dart @@ -2,7 +2,7 @@ import 'log_level.dart'; class LogEvent { final Level level; - final dynamic message; + final Object? message; final Object? error; final StackTrace? stackTrace; @@ -16,4 +16,20 @@ class LogEvent { this.error, this.stackTrace, }) : time = time ?? DateTime.now(); + + LogEvent copyWith({ + Level? level, + Object? message, + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) { + return LogEvent( + level ?? this.level, + message ?? this.message, + time: time ?? this.time, + error: error ?? this.error, + stackTrace: stackTrace ?? this.stackTrace, + ); + } } diff --git a/lib/src/log_filter.dart b/lib/src/log_filter.dart index af6bbd8..0bf1ef9 100644 --- a/lib/src/log_filter.dart +++ b/lib/src/log_filter.dart @@ -7,12 +7,10 @@ import 'logger.dart'; /// You can implement your own `LogFilter` or use [DevelopmentFilter]. /// Every implementation should consider [Logger.level]. abstract class LogFilter { - Level? _level; + late Logger logger; - // Still nullable for backwards compatibility. - Level? get level => _level ?? Logger.level; - - set level(Level? value) => _level = value; + /// Shortcut getter for [Logger.level]. + Level get level => logger.level; Future init() async {} @@ -20,7 +18,15 @@ abstract class LogFilter { /// it will be printed or canceled. /// /// Returns `true` if the message should be logged. - bool shouldLog(LogEvent event); + FilterResult shouldLog(LogEvent event); Future destroy() async {} } + +/// Describes the filter result of a log request. +enum FilterResult { + accept, + neutral, + deny, + ; +} diff --git a/lib/src/log_level.dart b/lib/src/log_level.dart index 1c89c1a..bc5dc15 100644 --- a/lib/src/log_level.dart +++ b/lib/src/log_level.dart @@ -2,18 +2,12 @@ /// levels above certain [Level]. enum Level { all(0), - @Deprecated('[verbose] is being deprecated in favor of [trace].') - verbose(999), trace(1000), debug(2000), info(3000), warning(4000), error(5000), - @Deprecated('[wtf] is being deprecated in favor of [fatal].') - wtf(5999), fatal(6000), - @Deprecated('[nothing] is being deprecated in favor of [off].') - nothing(9999), off(10000), ; diff --git a/lib/src/log_output.dart b/lib/src/log_output.dart index ab35e1a..d8c9e53 100644 --- a/lib/src/log_output.dart +++ b/lib/src/log_output.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'logger.dart'; import 'output_event.dart'; /// Log output receives a [OutputEvent] from [LogPrinter] and sends it to the @@ -8,6 +9,8 @@ import 'output_event.dart'; /// This can be an output stream, a file or a network target. [LogOutput] may /// cache multiple log messages. abstract class LogOutput { + late Logger logger; + Future init() async {} void output(OutputEvent event); diff --git a/lib/src/log_printer.dart b/lib/src/log_printer.dart index 6b4bf10..3832c45 100644 --- a/lib/src/log_printer.dart +++ b/lib/src/log_printer.dart @@ -1,4 +1,9 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'date_time_format.dart'; import 'log_event.dart'; +import 'logger.dart'; /// An abstract handler of log events. /// @@ -8,6 +13,94 @@ import 'log_event.dart'; /// /// You can implement a `LogPrinter` from scratch or extend [PrettyPrinter]. abstract class LogPrinter { + /// Matches a stacktrace line as generated on Android/iOS devices. + /// + /// For example: + /// * #1 Logger.log (package:logger/src/logger.dart:115:29) + static final deviceStackTraceRegex = RegExp(r'#[0-9]+\s+(.+) \((\S+)\)'); + + /// Matches a stacktrace line as generated by Flutter web. + /// + /// For example: + /// * packages/logger/src/printers/pretty_printer.dart 91:37 + static final webStackTraceRegex = RegExp(r'^((?:packages|dart-sdk)/\S+/)'); + + /// Matches a stacktrace line as generated by browser Dart. + /// + /// For example: + /// * dart:sdk_internal + /// * package:logger/src/logger.dart + static final browserStackTraceRegex = RegExp(r'^((?:package:)?dart:\S+|\S+)'); + + late Logger logger; + + /// Controls the format of [LogEvent.time]. + final DateTimeFormatter dateTimeFormat; + + /// Whether [LogEvent.time] is printed. + bool get printTimestamp => dateTimeFormat != DateTimeFormat.none; + + /// The index at which the stack trace should start. + /// + /// This can be useful if, for instance, Logger is wrapped in another class and + /// you wish to remove these wrapped calls from stack trace + /// + /// See also: + /// * [excludePaths] + final int stackTraceBeginIndex; + + /// A list of custom paths that are excluded from the stack trace. + /// + /// For example, to exclude your `MyLog` util that redirects to this logger: + /// ```dart + /// excludePaths: [ + /// // To exclude a whole package + /// "package:test", + /// // To exclude a single file + /// "package:test/util/my_log.dart", + /// ], + /// ``` + /// + /// See also: + /// * [stackTraceBeginIndex] + final List excludePaths; + + /// Controls the method count in stack traces + /// when no [LogEvent.error] was provided. + /// + /// In case no [LogEvent.stackTrace] was provided, + /// [StackTrace.current] will be used to create one. + /// + /// * Set to `0` in order to disable printing a stack trace + /// without an error parameter. + /// * Set to `null` to remove the method count limit all together. + /// + /// See also: + /// * [errorMethodCount] + final int? methodCount; + + /// Controls the method count in stack traces + /// when [LogEvent.error] was provided. + /// + /// In case no [LogEvent.stackTrace] was provided, + /// [StackTrace.current] will be used to create one. + /// + /// * Set to `0` in order to disable printing a stack trace + /// in case of an error parameter. + /// * Set to `null` to remove the method count limit all together. + /// + /// See also: + /// * [methodCount] + final int? errorMethodCount; + + LogPrinter({ + this.dateTimeFormat = DateTimeFormat.none, + this.stackTraceBeginIndex = 0, + this.excludePaths = const [], + this.methodCount = 0, + this.errorMethodCount = 30, + }); + Future init() async {} /// Is called every time a new [LogEvent] is sent and handles printing or @@ -15,4 +108,121 @@ abstract class LogPrinter { List log(LogEvent event); Future destroy() async {} + + String stringifyMessage(Object? message) { + if (message is String) return message; + + if (message is Map || message is Iterable) { + return encodeJson(message); + } else { + return message.toString(); + } + } + + String encodeJson(Object? message) { + var encoder = JsonEncoder.withIndent(' ', (object) => object.toString()); + return encoder.convert(message); + } + + String? getTime(DateTime time) { + if (printTimestamp) { + return dateTimeFormat(time); + } + return null; + } + + String? getStackTrace(LogEvent event) { + String? stackTraceStr; + if (event.error != null) { + if (errorMethodCount == null || errorMethodCount! > 0) { + stackTraceStr = formatStackTrace( + event.stackTrace ?? StackTrace.current, + methodCount: errorMethodCount, + ); + } + } else if (methodCount == null || methodCount! > 0) { + stackTraceStr = formatStackTrace( + event.stackTrace ?? StackTrace.current, + methodCount: methodCount, + ); + } + return stackTraceStr; + } + + String? formatStackTrace(StackTrace stackTrace, {int? methodCount}) { + List lines = stackTrace + .toString() + .split('\n') + .where( + (line) => + !discardDeviceStacktraceLine(line) && + !discardWebStacktraceLine(line) && + !discardBrowserStacktraceLine(line) && + line.isNotEmpty, + ) + .toList(); + List formatted = []; + + int stackTraceLength = + (methodCount != null ? min(lines.length, methodCount) : lines.length); + for (int count = 0; count < stackTraceLength; count++) { + var line = lines[count]; + if (count < stackTraceBeginIndex) { + continue; + } + formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}'); + } + + if (formatted.isEmpty) { + return null; + } else { + return formatted.join('\n'); + } + } + + bool isInExcludePaths(String segment) { + for (var element in excludePaths) { + if (segment.startsWith(element)) { + return true; + } + } + return false; + } + + bool discardDeviceStacktraceLine(String line) { + var match = deviceStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(2)!; + if (segment.startsWith('package:logger')) { + return true; + } + return isInExcludePaths(segment); + } + + bool discardWebStacktraceLine(String line) { + var match = webStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(1)!; + if (segment.startsWith('packages/logger') || + segment.startsWith('dart-sdk/lib')) { + return true; + } + return isInExcludePaths(segment); + } + + bool discardBrowserStacktraceLine(String line) { + var match = browserStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(1)!; + if (segment.startsWith('package:logger') || segment.startsWith('dart:')) { + return true; + } + return isInExcludePaths(segment); + } } diff --git a/lib/src/logger.dart b/lib/src/logger.dart index bcfc0b3..edfc8ae 100644 --- a/lib/src/logger.dart +++ b/lib/src/logger.dart @@ -14,13 +14,12 @@ typedef OutputCallback = void Function(OutputEvent event); /// Use instances of logger to send log messages to the [LogPrinter]. class Logger { - /// The current logging level of the app. - /// - /// All logs with levels below this level will be omitted. - static Level level = Level.trace; + /// The default logging level of new loggers. + static Level defaultLevel = Level.trace; /// The current default implementation of log filter. - static LogFilter Function() defaultFilter = () => DevelopmentFilter(); + static List Function() defaultFilters = + () => [DevelopmentFilter()]; /// The current default implementation of log printer. static LogPrinter Function() defaultPrinter = () => PrettyPrinter(); @@ -28,36 +27,49 @@ class Logger { /// The current default implementation of log output. static LogOutput Function() defaultOutput = () => ConsoleOutput(); - static final Set _logCallbacks = {}; + static final Set _globalLogCallbacks = {}; + + static final Set _globalOutputCallbacks = {}; + + final Set _logCallbacks = {}; - static final Set _outputCallbacks = {}; + final Set _outputCallbacks = {}; late final Future _initialization; - final LogFilter _filter; + + /// All logs with levels below this level will be omitted. + Level level; + + final List _filters; final LogPrinter _printer; final LogOutput _output; bool _active = true; /// Create a new instance of Logger. /// - /// You can provide a custom [printer], [filter] and [output]. Otherwise the + /// You can provide a custom [printers], [filters] and [outputs]. Otherwise the /// defaults: [PrettyPrinter], [DevelopmentFilter] and [ConsoleOutput] will be /// used. Logger({ - LogFilter? filter, + Level? level, + @Deprecated("Use [filters] instead.") LogFilter? filter, + List? filters, LogPrinter? printer, LogOutput? output, - Level? level, - }) : _filter = filter ?? defaultFilter(), + }) : level = level ?? defaultLevel, + _filters = filter != null ? [filter] : (filters ?? defaultFilters()), _printer = printer ?? defaultPrinter(), _output = output ?? defaultOutput() { - var filterInit = _filter.init(); - if (level != null) { - _filter.level = level; + assert(_filters.isNotEmpty, "Filters cannot be empty"); + + List initFutures = []; + for (var e in _filters) { + e.logger = this; + initFutures.add(e.init()); } - var printerInit = _printer.init(); - var outputInit = _output.init(); - _initialization = Future.wait([filterInit, printerInit, outputInit]); + initFutures.add(_printer.init()); + initFutures.add(_output.init()); + _initialization = Future.wait(initFutures); } /// Future indicating if the initialization of the @@ -67,21 +79,17 @@ class Logger { /// uses `async` in their `init` method. Future get init => _initialization; - /// Log a message at level [Level.verbose]. - @Deprecated( - "[Level.verbose] is being deprecated in favor of [Level.trace], use [t] instead.") - void v( - dynamic message, { - DateTime? time, - Object? error, - StackTrace? stackTrace, - }) { - t(message, time: time, error: error, stackTrace: stackTrace); - } + List get filter => _filters; + + LogPrinter get printer => _printer; - /// Log a message at level [Level.trace]. + LogOutput get output => _output; + + /// Logs a message at level [Level.trace]. + /// + /// {@macro log.parameters} void t( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -89,9 +97,11 @@ class Logger { log(Level.trace, message, time: time, error: error, stackTrace: stackTrace); } - /// Log a message at level [Level.debug]. + /// Logs a message at level [Level.debug]. + /// + /// {@macro log.parameters} void d( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -99,9 +109,11 @@ class Logger { log(Level.debug, message, time: time, error: error, stackTrace: stackTrace); } - /// Log a message at level [Level.info]. + /// Logs a message at level [Level.info]. + /// + /// {@macro log.parameters} void i( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -109,9 +121,11 @@ class Logger { log(Level.info, message, time: time, error: error, stackTrace: stackTrace); } - /// Log a message at level [Level.warning]. + /// Logs a message at level [Level.warning]. + /// + /// {@macro log.parameters} void w( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -120,9 +134,11 @@ class Logger { time: time, error: error, stackTrace: stackTrace); } - /// Log a message at level [Level.error]. + /// Logs a message at level [Level.error]. + /// + /// {@macro log.parameters} void e( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -130,21 +146,11 @@ class Logger { log(Level.error, message, time: time, error: error, stackTrace: stackTrace); } - /// Log a message at level [Level.wtf]. - @Deprecated( - "[Level.wtf] is being deprecated in favor of [Level.fatal], use [f] instead.") - void wtf( - dynamic message, { - DateTime? time, - Object? error, - StackTrace? stackTrace, - }) { - f(message, time: time, error: error, stackTrace: stackTrace); - } - - /// Log a message at level [Level.fatal]. + /// Logs a message at level [Level.fatal]. + /// + /// {@macro log.parameters} void f( - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -152,10 +158,17 @@ class Logger { log(Level.fatal, message, time: time, error: error, stackTrace: stackTrace); } - /// Log a message with [level]. + /// Logs a message with [level]. + /// + /// {@template log.parameters} + /// [message] can be a [String], [Iterable], [Map] or even a [Function] + /// to lazily evaluate the log statement. + /// + /// In case no [time] is provided, it defaults to [DateTime.now()]. + /// {@endtemplate} void log( Level level, - dynamic message, { + Object? message, { DateTime? time, Object? error, StackTrace? stackTrace, @@ -166,8 +179,7 @@ class Logger { throw ArgumentError('Error parameter cannot take a StackTrace!'); } else if (level == Level.all) { throw ArgumentError('Log events cannot have Level.all'); - // ignore: deprecated_member_use_from_same_package - } else if (level == Level.off || level == Level.nothing) { + } else if (level == Level.off) { throw ArgumentError('Log events cannot have Level.off'); } @@ -178,11 +190,16 @@ class Logger { error: error, stackTrace: stackTrace, ); - for (var callback in _logCallbacks) { + + var collectedLogCallbacks = [..._logCallbacks, ..._globalLogCallbacks]; + for (var callback in collectedLogCallbacks) { callback(logEvent); } - if (_filter.shouldLog(logEvent)) { + if (shouldLog(logEvent)) { + if (message is Function) { + logEvent = logEvent.copyWith(message: message()); + } var output = _printer.log(logEvent); if (output.isNotEmpty) { @@ -190,9 +207,14 @@ class Logger { // Issues with log output should NOT influence // the main software behavior. try { - for (var callback in _outputCallbacks) { + var collectedOutputCallbacks = [ + ..._outputCallbacks, + ..._globalOutputCallbacks, + ]; + for (var callback in collectedOutputCallbacks) { callback(outputEvent); } + _output.output(outputEvent); } catch (e, s) { print(e); @@ -202,6 +224,17 @@ class Logger { } } + bool shouldLog(LogEvent event) { + for (var filter in _filters) { + var result = filter.shouldLog(event); + if (result != FilterResult.neutral) { + return result == FilterResult.accept; + } + } + // Fallback in case all filters return neutral. + return true; + } + bool isClosed() { return !_active; } @@ -209,32 +242,56 @@ class Logger { /// Closes the logger and releases all resources. Future close() async { _active = false; - await _filter.destroy(); + await Future.wait(_filters.map((e) => e.destroy())); await _printer.destroy(); await _output.destroy(); } /// Register a [LogCallback] which is called for each new [LogEvent]. - static void addLogListener(LogCallback callback) { + void addLogListener(LogCallback callback) { _logCallbacks.add(callback); } /// Removes a [LogCallback] which was previously registered. /// /// Returns whether the callback was successfully removed. - static bool removeLogListener(LogCallback callback) { + bool removeLogListener(LogCallback callback) { return _logCallbacks.remove(callback); } /// Register an [OutputCallback] which is called for each new [OutputEvent]. - static void addOutputListener(OutputCallback callback) { + void addOutputListener(OutputCallback callback) { _outputCallbacks.add(callback); } /// Removes a [OutputCallback] which was previously registered. /// /// Returns whether the callback was successfully removed. - static void removeOutputListener(OutputCallback callback) { - _outputCallbacks.remove(callback); + bool removeOutputListener(OutputCallback callback) { + return _outputCallbacks.remove(callback); + } + + /// Register a global [LogCallback] which is called for each new [LogEvent] on each logger. + static void addGlobalLogListener(LogCallback callback) { + _globalLogCallbacks.add(callback); + } + + /// Removes a global [LogCallback] which was previously registered. + /// + /// Returns whether the callback was successfully removed. + static bool removeGlobalLogListener(LogCallback callback) { + return _globalLogCallbacks.remove(callback); + } + + /// Register a global [OutputCallback] which is called for each new [OutputEvent] on each logger. + static void addGlobalOutputListener(OutputCallback callback) { + _globalOutputCallbacks.add(callback); + } + + /// Removes a global [OutputCallback] which was previously registered. + /// + /// Returns whether the callback was successfully removed. + static bool removeGlobalOutputListener(OutputCallback callback) { + return _globalOutputCallbacks.remove(callback); } } diff --git a/lib/src/outputs/advanced_file_output.dart b/lib/src/outputs/advanced_file_output.dart index 4b8d525..a64768b 100644 --- a/lib/src/outputs/advanced_file_output.dart +++ b/lib/src/outputs/advanced_file_output.dart @@ -88,11 +88,9 @@ class AdvancedFileOutput extends LogOutput { _fileNameFormatter = fileNameFormatter ?? _defaultFileNameFormat, _writeImmediately = writeImmediately ?? [ - Level.error, Level.fatal, + Level.error, Level.warning, - // ignore: deprecated_member_use_from_same_package - Level.wtf, ], _maxRotatedFilesCount = maxRotatedFilesCount, _fileSorter = fileSorter ?? _defaultFileSorter, diff --git a/lib/src/outputs/file_output.dart b/lib/src/outputs/file_output.dart index a5bb5e5..eb4a1c5 100644 --- a/lib/src/outputs/file_output.dart +++ b/lib/src/outputs/file_output.dart @@ -1,25 +1,34 @@ import 'dart:convert'; import 'dart:io'; +import 'package:meta/meta.dart'; + import '../log_output.dart'; import '../output_event.dart'; /// Writes the log output to a file. class FileOutput extends LogOutput { - final File file; + late final File _file; final bool overrideExisting; final Encoding encoding; IOSink? _sink; FileOutput({ - required this.file, + required String path, this.overrideExisting = false, this.encoding = utf8, - }); + }) { + _file = createFile(path); + } + + @protected + File createFile(String path) { + return File(path); + } @override Future init() async { - _sink = file.openWrite( + _sink = _file.openWrite( mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend, encoding: encoding, ); diff --git a/lib/src/outputs/file_output_stub.dart b/lib/src/outputs/file_output_stub.dart index c992a22..868afe5 100644 --- a/lib/src/outputs/file_output_stub.dart +++ b/lib/src/outputs/file_output_stub.dart @@ -1,18 +1,26 @@ import 'dart:convert'; import 'dart:io'; +import 'package:meta/meta.dart'; + import '../log_output.dart'; import '../output_event.dart'; +/// Writes the log output to a file. class FileOutput extends LogOutput { FileOutput({ - required File file, + required String path, bool overrideExisting = false, Encoding encoding = utf8, }) { throw UnsupportedError("Not supported on this platform."); } + @protected + File createFile(String path) { + throw UnsupportedError("Not supported on this platform."); + } + @override void output(OutputEvent event) { throw UnsupportedError("Not supported on this platform."); diff --git a/lib/src/printers/hybrid_printer.dart b/lib/src/printers/hybrid_printer.dart index 9ceac6d..9eb2a3f 100644 --- a/lib/src/printers/hybrid_printer.dart +++ b/lib/src/printers/hybrid_printer.dart @@ -20,18 +20,14 @@ class HybridPrinter extends LogPrinter { LogPrinter realPrinter, { LogPrinter? debug, LogPrinter? trace, - @Deprecated('[verbose] is being deprecated in favor of [trace].') - LogPrinter? verbose, LogPrinter? fatal, - @Deprecated('[wtf] is being deprecated in favor of [fatal].') - LogPrinter? wtf, LogPrinter? info, LogPrinter? warning, LogPrinter? error, }) : _printerMap = { Level.debug: debug ?? realPrinter, - Level.trace: trace ?? verbose ?? realPrinter, - Level.fatal: fatal ?? wtf ?? realPrinter, + Level.trace: trace ?? realPrinter, + Level.fatal: fatal ?? realPrinter, Level.info: info ?? realPrinter, Level.warning: warning ?? realPrinter, Level.error: error ?? realPrinter, diff --git a/lib/src/printers/logfmt_printer.dart b/lib/src/printers/logfmt_printer.dart index de21b6a..5cf1b8f 100644 --- a/lib/src/printers/logfmt_printer.dart +++ b/lib/src/printers/logfmt_printer.dart @@ -1,3 +1,4 @@ +import '../date_time_format.dart'; import '../log_event.dart'; import '../log_level.dart'; import '../log_printer.dart'; @@ -16,20 +17,31 @@ class LogfmtPrinter extends LogPrinter { Level.fatal: 'fatal', }; + LogfmtPrinter({ + super.dateTimeFormat = DateTimeFormat.iso8601, + }); + @override List log(LogEvent event) { var output = StringBuffer('level=${levelPrefixes[event.level]}'); - if (event.message is String) { - output.write(' msg="${event.message}"'); - } else if (event.message is Map) { - event.message.entries.forEach((entry) { + var message = event.message; + + if (message is String) { + output.write(' msg="$message"'); + } else if (message is Map) { + for (var entry in message.entries) { if (entry.value is num) { output.write(' ${entry.key}=${entry.value}'); } else { output.write(' ${entry.key}="${entry.value}"'); } - }); + } } + + if (printTimestamp) { + output.write(' time="${getTime(event.time)}"'); + } + if (event.error != null) { output.write(' error="${event.error}"'); } diff --git a/lib/src/printers/prefix_printer.dart b/lib/src/printers/prefix_printer.dart index 6778263..e9ce176 100644 --- a/lib/src/printers/prefix_printer.dart +++ b/lib/src/printers/prefix_printer.dart @@ -20,17 +20,15 @@ class PrefixPrinter extends LogPrinter { this._realPrinter, { String? debug, String? trace, - @Deprecated('[verbose] is being deprecated in favor of [trace].') verbose, String? fatal, - @Deprecated('[wtf] is being deprecated in favor of [fatal].') wtf, String? info, String? warning, String? error, }) { _prefixMap = { Level.debug: debug ?? 'DEBUG', - Level.trace: trace ?? verbose ?? 'TRACE', - Level.fatal: fatal ?? wtf ?? 'FATAL', + Level.trace: trace ?? 'TRACE', + Level.fatal: fatal ?? 'FATAL', Level.info: info ?? 'INFO', Level.warning: warning ?? 'WARNING', Level.error: error ?? 'ERROR', diff --git a/lib/src/printers/pretty_printer.dart b/lib/src/printers/pretty_printer.dart index 7bee727..50ead0f 100644 --- a/lib/src/printers/pretty_printer.dart +++ b/lib/src/printers/pretty_printer.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:math'; - import '../ansi_color.dart'; import '../date_time_format.dart'; import '../log_event.dart'; @@ -45,65 +42,8 @@ class PrettyPrinter extends LogPrinter { Level.fatal: '👾', }; - /// Matches a stacktrace line as generated on Android/iOS devices. - /// - /// For example: - /// * #1 Logger.log (package:logger/src/logger.dart:115:29) - static final _deviceStackTraceRegex = RegExp(r'#[0-9]+\s+(.+) \((\S+)\)'); - - /// Matches a stacktrace line as generated by Flutter web. - /// - /// For example: - /// * packages/logger/src/printers/pretty_printer.dart 91:37 - static final _webStackTraceRegex = RegExp(r'^((?:packages|dart-sdk)/\S+/)'); - - /// Matches a stacktrace line as generated by browser Dart. - /// - /// For example: - /// * dart:sdk_internal - /// * package:logger/src/logger.dart - static final _browserStackTraceRegex = - RegExp(r'^((?:package:)?dart:\S+|\S+)'); - static DateTime? startTime; - /// The index at which the stack trace should start. - /// - /// This can be useful if, for instance, Logger is wrapped in another class and - /// you wish to remove these wrapped calls from stack trace - /// - /// See also: - /// * [excludePaths] - final int stackTraceBeginIndex; - - /// Controls the method count in stack traces - /// when no [LogEvent.error] was provided. - /// - /// In case no [LogEvent.stackTrace] was provided, - /// [StackTrace.current] will be used to create one. - /// - /// * Set to `0` in order to disable printing a stack trace - /// without an error parameter. - /// * Set to `null` to remove the method count limit all together. - /// - /// See also: - /// * [errorMethodCount] - final int? methodCount; - - /// Controls the method count in stack traces - /// when [LogEvent.error] was provided. - /// - /// In case no [LogEvent.stackTrace] was provided, - /// [StackTrace.current] will be used to create one. - /// - /// * Set to `0` in order to disable printing a stack trace - /// in case of an error parameter. - /// * Set to `null` to remove the method count limit all together. - /// - /// See also: - /// * [methodCount] - final int? errorMethodCount; - /// Controls the length of the divider lines. final int lineLength; @@ -113,13 +53,6 @@ class PrettyPrinter extends LogPrinter { /// Whether emojis are prefixed to the log line. final bool printEmojis; - /// Whether [LogEvent.time] is printed. - @Deprecated("Use `dateTimeFormat` instead.") - bool get printTime => dateTimeFormat != DateTimeFormat.none; - - /// Controls the format of [LogEvent.time]. - final DateTimeFormatter dateTimeFormat; - /// Controls the ascii 'boxing' of different [Level]s. /// /// By default all levels are 'boxed', @@ -157,22 +90,6 @@ class PrettyPrinter extends LogPrinter { /// * [excludeBox] final bool noBoxingByDefault; - /// A list of custom paths that are excluded from the stack trace. - /// - /// For example, to exclude your `MyLog` util that redirects to this logger: - /// ```dart - /// excludePaths: [ - /// // To exclude a whole package - /// "package:test", - /// // To exclude a single file - /// "package:test/util/my_log.dart", - /// ], - /// ``` - /// - /// See also: - /// * [stackTraceBeginIndex] - final List excludePaths; - /// Contains the parsed rules resulting from [excludeBox] and [noBoxingByDefault]. late final Map _includeBox; String _topBorder = ''; @@ -190,30 +107,19 @@ class PrettyPrinter extends LogPrinter { final Map? levelEmojis; PrettyPrinter({ - this.stackTraceBeginIndex = 0, - this.methodCount = 2, - this.errorMethodCount = 8, + super.stackTraceBeginIndex, + super.methodCount, + super.errorMethodCount, this.lineLength = 120, this.colors = true, this.printEmojis = true, - @Deprecated( - "Use `dateTimeFormat` with `DateTimeFormat.onlyTimeAndSinceStart` or `DateTimeFormat.none` instead.") - bool? printTime, - DateTimeFormatter dateTimeFormat = DateTimeFormat.none, + super.dateTimeFormat = DateTimeFormat.onlyTimeAndSinceStart, this.excludeBox = const {}, this.noBoxingByDefault = false, - this.excludePaths = const [], + super.excludePaths, this.levelColors, this.levelEmojis, - }) : assert( - (printTime != null && dateTimeFormat == DateTimeFormat.none) || - printTime == null, - "Don't set printTime when using dateTimeFormat"), - dateTimeFormat = printTime == null - ? dateTimeFormat - : (printTime - ? DateTimeFormat.onlyTimeAndSinceStart - : DateTimeFormat.none) { + }) { startTime ??= DateTime.now(); var doubleDividerLine = StringBuffer(); @@ -237,31 +143,10 @@ class PrettyPrinter extends LogPrinter { @override List log(LogEvent event) { - var messageStr = stringifyMessage(event.message); - - String? stackTraceStr; - if (event.error != null) { - if ((errorMethodCount == null || errorMethodCount! > 0)) { - stackTraceStr = formatStackTrace( - event.stackTrace ?? StackTrace.current, - errorMethodCount, - ); - } - } else if (methodCount == null || methodCount! > 0) { - stackTraceStr = formatStackTrace( - event.stackTrace ?? StackTrace.current, - methodCount, - ); - } - - var errorStr = event.error?.toString(); - - String? timeStr; - // Keep backwards-compatibility to `printTime` check - // ignore: deprecated_member_use_from_same_package - if (printTime) { - timeStr = getTime(event.time); - } + String messageStr = stringifyMessage(event.message); + String? stackTraceStr = getStackTrace(event); + String? timeStr = getTime(event.time); + String? errorStr = event.error?.toString(); return _formatAndPrint( event.level, @@ -272,102 +157,6 @@ class PrettyPrinter extends LogPrinter { ); } - String? formatStackTrace(StackTrace? stackTrace, int? methodCount) { - List lines = stackTrace - .toString() - .split('\n') - .where( - (line) => - !_discardDeviceStacktraceLine(line) && - !_discardWebStacktraceLine(line) && - !_discardBrowserStacktraceLine(line) && - line.isNotEmpty, - ) - .toList(); - List formatted = []; - - int stackTraceLength = - (methodCount != null ? min(lines.length, methodCount) : lines.length); - for (int count = 0; count < stackTraceLength; count++) { - var line = lines[count]; - if (count < stackTraceBeginIndex) { - continue; - } - formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}'); - } - - if (formatted.isEmpty) { - return null; - } else { - return formatted.join('\n'); - } - } - - bool _isInExcludePaths(String segment) { - for (var element in excludePaths) { - if (segment.startsWith(element)) { - return true; - } - } - return false; - } - - bool _discardDeviceStacktraceLine(String line) { - var match = _deviceStackTraceRegex.matchAsPrefix(line); - if (match == null) { - return false; - } - final segment = match.group(2)!; - if (segment.startsWith('package:logger')) { - return true; - } - return _isInExcludePaths(segment); - } - - bool _discardWebStacktraceLine(String line) { - var match = _webStackTraceRegex.matchAsPrefix(line); - if (match == null) { - return false; - } - final segment = match.group(1)!; - if (segment.startsWith('packages/logger') || - segment.startsWith('dart-sdk/lib')) { - return true; - } - return _isInExcludePaths(segment); - } - - bool _discardBrowserStacktraceLine(String line) { - var match = _browserStackTraceRegex.matchAsPrefix(line); - if (match == null) { - return false; - } - final segment = match.group(1)!; - if (segment.startsWith('package:logger') || segment.startsWith('dart:')) { - return true; - } - return _isInExcludePaths(segment); - } - - String getTime(DateTime time) { - return dateTimeFormat(time); - } - - // Handles any object that is causing JsonEncoder() problems - Object toEncodableFallback(dynamic object) { - return object.toString(); - } - - String stringifyMessage(dynamic message) { - final finalMessage = message is Function ? message() : message; - if (finalMessage is Map || finalMessage is Iterable) { - var encoder = JsonEncoder.withIndent(' ', toEncodableFallback); - return encoder.convert(finalMessage); - } else { - return finalMessage.toString(); - } - } - AnsiColor _getLevelColor(Level level) { AnsiColor? color; if (colors) { diff --git a/lib/src/printers/simple_printer.dart b/lib/src/printers/simple_printer.dart index d56fe4d..90d8f95 100644 --- a/lib/src/printers/simple_printer.dart +++ b/lib/src/printers/simple_printer.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import '../ansi_color.dart'; +import '../date_time_format.dart'; import '../log_event.dart'; import '../log_level.dart'; import '../log_printer.dart'; @@ -28,16 +29,18 @@ class SimplePrinter extends LogPrinter { Level.fatal: const AnsiColor.fg(199), }; - final bool printTime; final bool colors; - SimplePrinter({this.printTime = false, this.colors = true}); + SimplePrinter({ + super.dateTimeFormat = DateTimeFormat.iso8601, + this.colors = true, + }); @override List log(LogEvent event) { - var messageStr = _stringifyMessage(event.message); + var messageStr = stringifyMessage(event.message); var errorStr = event.error != null ? ' ERROR: ${event.error}' : ''; - var timeStr = printTime ? 'TIME: ${event.time.toIso8601String()}' : ''; + var timeStr = printTimestamp ? 'TIME: ${getTime(event.time)}' : ''; return ['${_labelFor(event.level)} $timeStr $messageStr$errorStr']; } @@ -48,13 +51,9 @@ class SimplePrinter extends LogPrinter { return colors ? color(prefix) : prefix; } - String _stringifyMessage(dynamic message) { - final finalMessage = message is Function ? message() : message; - if (finalMessage is Map || finalMessage is Iterable) { - var encoder = const JsonEncoder.withIndent(null); - return encoder.convert(finalMessage); - } else { - return finalMessage.toString(); - } + @override + String encodeJson(Object? message) { + var encoder = const JsonEncoder.withIndent(null); + return encoder.convert(message); } } diff --git a/lib/web.dart b/lib/web.dart deleted file mode 100644 index 10cc405..0000000 --- a/lib/web.dart +++ /dev/null @@ -1,23 +0,0 @@ -/// Web-safe logger. -library web; - -export 'src/ansi_color.dart'; -export 'src/date_time_format.dart'; -export 'src/filters/development_filter.dart'; -export 'src/filters/production_filter.dart'; -export 'src/log_event.dart'; -export 'src/log_filter.dart'; -export 'src/log_level.dart'; -export 'src/log_output.dart'; -export 'src/log_printer.dart'; -export 'src/logger.dart'; -export 'src/output_event.dart'; -export 'src/outputs/console_output.dart'; -export 'src/outputs/memory_output.dart'; -export 'src/outputs/multi_output.dart'; -export 'src/outputs/stream_output.dart'; -export 'src/printers/hybrid_printer.dart'; -export 'src/printers/logfmt_printer.dart'; -export 'src/printers/prefix_printer.dart'; -export 'src/printers/pretty_printer.dart'; -export 'src/printers/simple_printer.dart'; diff --git a/test/listener_test.dart b/test/listener_test.dart new file mode 100644 index 0000000..194670a --- /dev/null +++ b/test/listener_test.dart @@ -0,0 +1,182 @@ +import 'package:logger/logger.dart'; +import 'package:test/test.dart'; + +import 'logger_test.dart'; + +class NoOutput extends LogOutput { + @override + void output(OutputEvent event) { + // No-op. + } +} + +void main() { + group("Local", () { + LogEvent? loggedLogEvent; + logCallback(LogEvent event) { + loggedLogEvent = event; + } + + OutputEvent? loggedOutputEvent; + outputCallback(OutputEvent event) { + loggedOutputEvent = event; + } + + test('LogListener', () { + var stackTrace = StackTrace.current; + + var logger = Logger(filter: NeverFilter(), output: NoOutput()); + logger.addLogListener(logCallback); + logger.i('Test', error: 'Error', stackTrace: stackTrace); + + expect(loggedLogEvent?.level, Level.info); + expect(loggedLogEvent?.message, 'Test'); + expect(loggedLogEvent?.error, 'Error'); + expect(loggedLogEvent?.stackTrace, stackTrace); + + logger.removeLogListener(logCallback); + logger.i('Test2'); + + // event stays the same + expect(loggedLogEvent?.level, Level.info); + expect(loggedLogEvent?.message, 'Test'); + expect(loggedLogEvent?.error, 'Error'); + expect(loggedLogEvent?.stackTrace, stackTrace); + }); + + test('OutputListener', () { + var stackTrace = StackTrace.current; + + var logger = Logger(filter: AlwaysFilter(), output: NoOutput()); + logger.addOutputListener(outputCallback); + logger.i('Test', error: 'Error', stackTrace: stackTrace); + + expect(loggedOutputEvent?.origin.level, Level.info); + expect(loggedOutputEvent?.origin.message, 'Test'); + expect(loggedOutputEvent?.origin.error, 'Error'); + expect(loggedOutputEvent?.origin.stackTrace, stackTrace); + + logger.removeOutputListener(outputCallback); + logger.i('Test2'); + + // event stays the same + expect(loggedOutputEvent?.origin.level, Level.info); + expect(loggedOutputEvent?.origin.message, 'Test'); + expect(loggedOutputEvent?.origin.error, 'Error'); + expect(loggedOutputEvent?.origin.stackTrace, stackTrace); + }); + + test('OutputListener Filter', () { + var stackTrace = StackTrace.current; + OutputEvent? loggedEvent; + callback(OutputEvent event) { + loggedEvent = event; + } + + var logger = Logger(filter: NeverFilter(), output: NoOutput()); + logger.addOutputListener(callback); + logger.i('Test', error: 'Error', stackTrace: stackTrace); + + expect(loggedEvent, isNull); + + logger.close(); + logger = Logger(filter: AlwaysFilter(), output: NoOutput()); + logger.addOutputListener(callback); + + logger.i('Test', error: 'Error', stackTrace: stackTrace); + + expect(loggedEvent?.origin.level, Level.info); + expect(loggedEvent?.origin.message, 'Test'); + expect(loggedEvent?.origin.error, 'Error'); + expect(loggedEvent?.origin.stackTrace, stackTrace); + + logger.removeOutputListener(callback); + logger.i('Test2'); + + // event stays the same + expect(loggedEvent?.origin.level, Level.info); + expect(loggedEvent?.origin.message, 'Test'); + expect(loggedEvent?.origin.error, 'Error'); + expect(loggedEvent?.origin.stackTrace, stackTrace); + }); + }); + + group("Global", () { + LogEvent? localEvent; + localCallback(LogEvent event) { + localEvent = event; + } + + LogEvent? globalEvent; + globalCallback(LogEvent event) { + globalEvent = event; + } + + OutputEvent? localOutputEvent; + localOutputCallback(OutputEvent event) { + localOutputEvent = event; + } + + OutputEvent? globalOutputEvent; + globalOutputCallback(OutputEvent event) { + globalOutputEvent = event; + } + + setUp(() { + Logger.addGlobalLogListener(globalCallback); + Logger.addGlobalOutputListener(globalOutputCallback); + }); + + tearDown(() { + Logger.removeGlobalLogListener(globalCallback); + localEvent = null; + globalEvent = null; + + Logger.removeGlobalOutputListener(globalOutputCallback); + localOutputEvent = null; + globalOutputEvent = null; + }); + + test(' LogListener', () { + var logger = Logger(filter: NeverFilter(), output: NoOutput()); + var secondLogger = Logger(filter: NeverFilter(), output: NoOutput()); + + secondLogger.addLogListener(localCallback); + + logger.i('Test'); + expect(globalEvent, isNotNull); + expect(localEvent, isNull); + + secondLogger.i('Test2'); + expect(globalEvent?.message, 'Test2'); + expect(localEvent?.message, 'Test2'); + + Logger.removeGlobalLogListener(globalCallback); + logger.i('Test3'); + + // event stays the same + expect(globalEvent?.message, 'Test2'); + }); + + test(' OutputListener', () { + var logger = Logger(filter: AlwaysFilter(), output: NoOutput()); + var secondLogger = Logger(filter: AlwaysFilter(), output: NoOutput()); + + secondLogger.addOutputListener(localOutputCallback); + + logger.i('Test'); + expect(globalOutputEvent, isNotNull); + expect(localOutputEvent, isNull); + + secondLogger.i('Test2'); + expect(globalOutputEvent?.origin.message, 'Test2'); + expect(localOutputEvent?.origin.message, 'Test2'); + + Logger.removeGlobalOutputListener(globalOutputCallback); + logger.i('Test3'); + + // event stays the same + expect(globalOutputEvent?.origin.message, 'Test2'); + }); + }); +} diff --git a/test/logger_test.dart b/test/logger_test.dart index f2d5768..5b9999a 100644 --- a/test/logger_test.dart +++ b/test/logger_test.dart @@ -5,19 +5,19 @@ import 'package:test/test.dart'; typedef PrinterCallback = List Function( Level level, - dynamic message, + Object? message, Object? error, StackTrace? stackTrace, ); -class _AlwaysFilter extends LogFilter { +class AlwaysFilter extends LogFilter { @override - bool shouldLog(LogEvent event) => true; + FilterResult shouldLog(LogEvent event) => FilterResult.accept; } -class _NeverFilter extends LogFilter { +class NeverFilter extends LogFilter { @override - bool shouldLog(LogEvent event) => false; + FilterResult shouldLog(LogEvent event) => FilterResult.deny; } class _CallbackPrinter extends LogPrinter { @@ -49,7 +49,7 @@ class _AsyncFilter extends LogFilter { } @override - bool shouldLog(LogEvent event) => false; + FilterResult shouldLog(LogEvent event) => FilterResult.accept; } class _AsyncPrinter extends LogPrinter { @@ -86,21 +86,10 @@ class _AsyncOutput extends LogOutput { } } -/// Test class for the lazy-initialization of variables. -class LazyLogger { - static bool? printed; - static final filter = ProductionFilter(); - static final printer = _CallbackPrinter((l, m, e, s) { - printed = true; - return []; - }); - static final logger = Logger(filter: filter, printer: printer); -} - void main() { Level? printedLevel; - dynamic printedMessage; - dynamic printedError; + Object? printedMessage; + Object? printedError; StackTrace? printedStackTrace; var callbackPrinter = _CallbackPrinter((l, m, e, s) { printedLevel = l; @@ -118,12 +107,12 @@ void main() { }); test('Logger.log', () { - var logger = Logger(filter: _NeverFilter(), printer: callbackPrinter); + var logger = Logger(filters: [NeverFilter()], printer: callbackPrinter); logger.log(Level.debug, 'Some message'); expect(printedMessage, null); - logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var levels = [ Level.trace, @@ -181,7 +170,7 @@ void main() { }); test('Logger.t', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.t('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.trace); @@ -191,7 +180,7 @@ void main() { }); test('Logger.d', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.d('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.debug); @@ -201,7 +190,7 @@ void main() { }); test('Logger.i', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.i('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.info); @@ -211,7 +200,7 @@ void main() { }); test('Logger.w', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.w('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.warning); @@ -221,7 +210,7 @@ void main() { }); test('Logger.e', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.e('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.error); @@ -231,7 +220,7 @@ void main() { }); test('Logger.f', () { - var logger = Logger(filter: _AlwaysFilter(), printer: callbackPrinter); + var logger = Logger(filters: [AlwaysFilter()], printer: callbackPrinter); var stackTrace = StackTrace.current; logger.f('Test', error: 'Error', stackTrace: stackTrace); expect(printedLevel, Level.fatal); @@ -240,10 +229,18 @@ void main() { expect(printedStackTrace, stackTrace); }); - test('setting log level above log level of message', () { + test('Deal with function messages', () { + final heavyComputation = 'heavily computed very pretty Message'; + + var logger = Logger(filter: AlwaysFilter(), printer: callbackPrinter); + logger.f(() => heavyComputation); + expect(printedMessage, heavyComputation); + }); + + test('Setting log level above log level of message', () { printedMessage = null; var logger = Logger( - filter: ProductionFilter(), + filters: [ProductionFilter()], printer: callbackPrinter, level: Level.warning, ); @@ -255,21 +252,17 @@ void main() { expect(printedMessage, 'This is'); }); - test('Setting filter Levels', () { - var filter = ProductionFilter(); - expect(filter.level, Logger.level); - + test('Setting log level', () { final initLevel = Level.warning; - // ignore: unused_local_variable var logger = Logger( - filter: filter, + filters: [ProductionFilter()], printer: callbackPrinter, level: initLevel, ); - expect(filter.level, initLevel); + expect(logger.level, initLevel); - filter.level = Level.fatal; - expect(filter.level, Level.fatal); + logger.level = Level.fatal; + expect(logger.level, Level.fatal); }); test('Logger.close', () async { @@ -279,17 +272,10 @@ void main() { expect(logger.isClosed(), true); }); - test('Lazy Logger Initialization', () { - expect(LazyLogger.printed, isNull); - LazyLogger.filter.level = Level.warning; - LazyLogger.logger.i("This is an info message and should not show"); - expect(LazyLogger.printed, isNull); - }); - test('Async Filter Initialization', () async { var comp = _AsyncFilter(const Duration(milliseconds: 100)); var logger = Logger( - filter: comp, + filters: [comp], ); expect(comp.initialized, false); diff --git a/test/outputs/file_output_test.dart b/test/outputs/file_output_test.dart index 02ebb32..810ff50 100644 --- a/test/outputs/file_output_test.dart +++ b/test/outputs/file_output_test.dart @@ -1,20 +1,39 @@ +import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:logger/logger.dart'; import 'package:test/test.dart'; var memory = MemoryFileSystem(); +class MemoryFileOutput extends FileOutput { + MemoryFileOutput({ + required super.path, + super.encoding, + super.overrideExisting, + }); + + late File _file; + + get file => _file; + + @override + File createFile(String path) { + return _file = memory.file(path); + } +} + void main() { - final file = memory.file("dart_logger_test.log"); + final fileName = "dart_logger_test.log"; tearDown(() { + var file = memory.file(fileName); if (file.existsSync()) { file.deleteSync(); } }); test('Real file read and write', () async { - var output = FileOutput(file: file); + var output = MemoryFileOutput(path: fileName); await output.init(); final event0 = OutputEvent(LogEvent(Level.info, ""), ["First event"]); @@ -27,7 +46,7 @@ void main() { await output.destroy(); - var content = await file.readAsString(); + var content = await output.file.readAsString(); expect( content, allOf( diff --git a/test/printers/logfmt_printer_test.dart b/test/printers/logfmt_printer_test.dart index 8212cbe..02015ef 100644 --- a/test/printers/logfmt_printer_test.dart +++ b/test/printers/logfmt_printer_test.dart @@ -40,6 +40,20 @@ void main() { expect(output, contains('foo="bar baz"')); }); + test('includes time', () { + var timestamp = DateTime.now(); + expect( + printer.log(LogEvent( + Level.debug, + 'some message', + error: Exception('boom'), + stackTrace: StackTrace.current, + time: timestamp, + ))[0], + contains('time="${timestamp.toIso8601String()}"'), + ); + }); + test('handles an error/exception', () { var output = printer.log(LogEvent( Level.debug, diff --git a/test/printers/pretty_printer_test.dart b/test/printers/pretty_printer_test.dart index b0eaad8..b09a323 100644 --- a/test/printers/pretty_printer_test.dart +++ b/test/printers/pretty_printer_test.dart @@ -156,25 +156,6 @@ void main() { } }); - test('deal with Function type message', () { - final prettyPrinter = PrettyPrinter(); - final expectedMessage = 'heavily computed very pretty Message'; - final withFunction = LogEvent( - Level.debug, - () => expectedMessage, - error: 'some error', - stackTrace: StackTrace.current, - ); - - final actualLog = prettyPrinter.log(withFunction); - final actualLogString = readMessage(actualLog); - - expect( - actualLogString, - contains(expectedMessage), - ); - }); - test('stackTraceBeginIndex', () { final prettyPrinter = PrettyPrinter( stackTraceBeginIndex: 2, diff --git a/test/printers/simple_printer_test.dart b/test/printers/simple_printer_test.dart index 5b1c173..4303e86 100644 --- a/test/printers/simple_printer_test.dart +++ b/test/printers/simple_printer_test.dart @@ -11,7 +11,8 @@ void main() { stackTrace: StackTrace.current, ); - var plainPrinter = SimplePrinter(colors: false, printTime: false); + var plainPrinter = + SimplePrinter(colors: false, dateTimeFormat: DateTimeFormat.none); test('represent event on a single line (ignoring stacktrace)', () { var outputs = plainPrinter.log(event); @@ -37,13 +38,13 @@ void main() { }); test('print time', () { - var printer = SimplePrinter(printTime: true); + var printer = SimplePrinter(); expect(printer.log(event)[0], contains('TIME')); }); test('does not print time', () { - var printer = SimplePrinter(printTime: false); + var printer = SimplePrinter(dateTimeFormat: DateTimeFormat.none); expect(printer.log(event)[0], isNot(contains('TIME'))); }); @@ -87,19 +88,4 @@ void main() { '[D] [1,2,3,4] ERROR: some error', ); }); - - test('deal with Function type message', () { - var expectedMessage = 'heavily computed Message'; - var withFunction = LogEvent( - Level.debug, - () => expectedMessage, - error: 'some error', - stackTrace: StackTrace.current, - ); - - expect( - plainPrinter.log(withFunction)[0], - '[D] $expectedMessage ERROR: some error', - ); - }); }