Skip to content

Commit

Permalink
MIGRATE: Removed unpublished dep for publishing, added CallerLogger i…
Browse files Browse the repository at this point in the history
…n its place from logger package

squash

squash
  • Loading branch information
juskek committed Sep 28, 2022
1 parent b74b17e commit fb5e288
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 77 deletions.
29 changes: 0 additions & 29 deletions example/main.dart

This file was deleted.

98 changes: 98 additions & 0 deletions example/main_caller_logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'dart:convert';

import 'package:flutter_dev_utils/src/caller_logger/caller_logger.dart';
import 'package:flutter_dev_utils/src/caller_logger/type_filter.dart';
import 'package:logger/logger.dart';

var logger = CallerLogger(
ignoreCallers: {
'syncTryCatchHandler',
},
filter: TypeFilter(
ignoreTypes: {
IgnoredClass,
},
ignoreLevel: Level.warning,
),
level: Level.verbose,
);

void main() {
print(
'Run with either `dart example/main_caller_logger.dart` or `dart --enable-asserts example/main_caller_logger.dart`.');
demo();
}

void demo() {
/// Settings are such that:
///
/// 1. All logs in ExampleClass are printed (because level: Level.verbose)
/// 2. Logs in IgnoredClass are not printed (because ignoreTypes: IgnoredClass),
/// except for logs at level of warning and above (because ignoreLevel: Level.warning)
ExampleClass.run();
print('========================================');
IgnoredClass.run();
print('========================================');

/// 3. Printed log for this will show ExampleClass.nestedRun() instead of
/// syncTryCatchHandler (because ignoreCallers: {'syncTryCatchHandler'})
ExampleClass.nestedRun();
}

class ExampleClass {
static void run() {
logger.log(Level.verbose, 'log: verbose');

logger.v('verbose');
logger.d('debug');
logger.i('info');
logger.w('warning');
logger.e('error');
logger.wtf('wtf');
}

static void nestedRun() {
// nested functions will print
syncTryCatchHandler(
tryFunction: () => jsonDecode('thisIsNotJson'),
);
}
}

class IgnoredClass {
static void run() {
logger.v('verbose');
logger.d('debug');
logger.i('info');
logger.w('warning');
logger.e('error');
logger.wtf('wtf');
}
}

/// Synchronous try and catch handler to reduce boilerplate
///
/// Used as an example to show how nested functions can be ignored in [ignoreCallers]
dynamic syncTryCatchHandler(
{dynamic Function()? tryFunction,
Map<Exception, dynamic Function()?>? catchKnownExceptions,
dynamic Function()? catchUnknownExceptions}) {
try {
try {
return tryFunction?.call();
} on Exception catch (e, s) {
if (catchKnownExceptions == null) {
rethrow;
} else if (catchKnownExceptions.containsKey(e)) {
logger.w('handling known exception', e, s);
catchKnownExceptions[e]?.call();
} else {
rethrow;
}
}
} catch (e, s) {
logger.e('catchUnknown:', e, s);
catchUnknownExceptions?.call();
}
}
33 changes: 33 additions & 0 deletions example/main_try_catch_handler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'dart:convert';

import 'package:flutter_dev_utils/flutter_dev_utils.dart';
import 'package:flutter_dev_utils/src/caller_logger/caller_logger.dart';
import 'package:logger/logger.dart';

void main() {
print(
'Run with either `dart example/main_try_catch_handler.dart` or `dart --enable-asserts example/main_try_catch_handler.dart`.');
Demo.run();
}

class Demo {
static void run() {
syncTryCatchHandler(tryFunction: () {
CallerLogger().i('It works!'); // this should print
return true;
});

asyncTryCatchHandler(tryFunction: () async {
CallerLogger().i('It works!'); // this should print
return true;
});
syncTryCatchHandler(
tryFunction: () =>
jsonDecode('notJson'), // this should throw an exception
);
asyncTryCatchHandler(
tryFunction: () =>
jsonDecode('notJson'), // this should throw an exception
);
}
}
6 changes: 4 additions & 2 deletions lib/flutter_dev_utils.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
library flutter_dev_utils;

export 'src/try_catch_handler/sync_try_catch_handler.dart';
export 'src/try_catch_handler/async_try_catch_handler.dart';
export 'package:flutter_dev_utils/src/try_catch_handler/sync_try_catch_handler.dart';
export 'package:flutter_dev_utils/src/try_catch_handler/async_try_catch_handler.dart';
export 'package:flutter_dev_utils/src/caller_logger/caller_logger.dart';
export 'package:flutter_dev_utils/src/caller_logger/type_filter.dart';
66 changes: 66 additions & 0 deletions lib/src/caller_logger/caller_logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:flutter_dev_utils/src/caller_logger/caller_printer.dart';
import 'package:logger/logger.dart';

/// Custom Logger which prints the caller of the Logger.log() method.
///
/// E.g., if ExampleClass1.method() calls logger.d('some debug message'),
/// then the console will show: ExampleClass1.method: some debug message
///
/// [ignoreCallers] can be specified to show the most relevant caller
class CallerLogger extends Logger {
CallerLogger._({
LogFilter? filter,
LogPrinter? printer,
LogOutput? output,
Level? level,
required Set<String> ignoreCallers,
}) : _ignoreCallers = ignoreCallers,
super(filter: filter, printer: printer, output: output, level: level);

factory CallerLogger({
LogFilter? filter,
LogPrinter? printer,
LogOutput? output,
Level? level,
Set<String>? ignoreCallers,
}) {
/// Skip callers in stack trace so that the more relevant caller is printed,
/// e.g., methods from the loggers or utility functions
final _defaultIgnoreCallers = {
'CallerLogger.log',
'Logger.',
};
if (ignoreCallers == null) {
ignoreCallers = _defaultIgnoreCallers;
} else {
ignoreCallers.addAll(_defaultIgnoreCallers);
}
return CallerLogger._(
filter: filter,
printer: printer ?? CallerPrinter(),
output: output,
level: level,
ignoreCallers: ignoreCallers,
);
}

Set<String> _ignoreCallers;

@override
void log(Level level, message, [error, StackTrace? stackTrace]) {
// get caller
final lines = StackTrace.current.toString().split('\n');
var caller = 'CallerNotFound';
for (var line in lines) {
if (_ignoreCallers.any((element) => line.contains(element))) {
continue;
} else {
// ! caller in StackTrace #x seems to be i=6 when split by spaces
// ! this may bug out if formatting of StackTrace changes
caller = line.split(' ')[6].trim();
break; // exit loop to save first caller which is not from the logger
}
}
super.log(level, caller + ': ' + message, error, stackTrace);
}
}
159 changes: 159 additions & 0 deletions lib/src/caller_logger/caller_printer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import 'dart:convert';

import 'package:logger/logger.dart';

class CallerPrinter extends LogPrinter {
static final levelPrefixes = {
Level.verbose: '[V]',
Level.debug: '[D]',
Level.info: '[I]',
Level.warning: '[W]',
Level.error: '[E]',
Level.wtf: '[WTF]',
};

static final levelColors = {
Level.verbose: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.debug: AnsiColor.none(),
Level.info: AnsiColor.fg(12),
Level.warning: AnsiColor.fg(208),
Level.error: AnsiColor.fg(196),
Level.wtf: AnsiColor.fg(199),
};

final bool printTime;
final bool colors;

/// The index which to begin the stack trace at
///
/// This can be useful if, for instance, Logger is wrapped in another class and
/// you wish to remove these wrapped calls from stack trace
final int stackTraceBeginIndex;

// No of methods to show in StackTrace when no error or exception is thrown
final int methodCount;

/// No of methods to show in StackTrace when error or exception is thrown
final int errorMethodCount;

CallerPrinter({
this.printTime = false,
this.colors = false,
// StackTrace args
this.stackTraceBeginIndex = 0,
this.methodCount = 0,
this.errorMethodCount = 10,
});
@override
List<String> log(LogEvent event) {
var messageStr = _stringifyMessage(event.message);
var errorStr = event.error != null ? '\nERROR: ${event.error}' : '';
var timeStr = printTime ? 'TIME: ${DateTime.now().toIso8601String()}' : '';

var stackTraceStr;
if (event.stackTrace == null) {
if (methodCount > 0) {
stackTraceStr = _formatStackTrace(StackTrace.current, methodCount);
}
} else if (errorMethodCount > 0) {
stackTraceStr = _formatStackTrace(event.stackTrace, errorMethodCount);
}
stackTraceStr = stackTraceStr == null
? ''
: '\n' + stackTraceStr + '\n========================================';

return [
'${_labelFor(event.level)} $timeStr$messageStr$errorStr$stackTraceStr'
];
}

String _labelFor(Level level) {
var prefix = levelPrefixes[level]!;
var color = levelColors[level]!;

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 = JsonEncoder.withIndent(null);
return encoder.convert(finalMessage);
} else {
return finalMessage.toString();
}
}

//! Stack Trace formatting
/// 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]+)');

String? _formatStackTrace(StackTrace? stackTrace, int methodCount) {
var lines = stackTrace.toString().split('\n');
if (stackTraceBeginIndex > 0 && stackTraceBeginIndex < lines.length - 1) {
lines = lines.sublist(stackTraceBeginIndex);
}
var formatted = <String>[];
var count = 0;
for (var line in lines) {
if (_discardDeviceStacktraceLine(line) ||
_discardWebStacktraceLine(line) ||
_discardBrowserStacktraceLine(line) ||
line.isEmpty) {
continue;
}
formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}');
if (++count == methodCount) {
break;
}
}

if (formatted.isEmpty) {
return null;
} else {
return formatted.join('\n');
}
}

bool _discardDeviceStacktraceLine(String line) {
var match = _deviceStackTraceRegex.matchAsPrefix(line);
if (match == null) {
return false;
}
return match.group(2)!.startsWith('package:logger');
}

bool _discardWebStacktraceLine(String line) {
var match = _webStackTraceRegex.matchAsPrefix(line);
if (match == null) {
return false;
}
return match.group(1)!.startsWith('packages/logger') ||
match.group(1)!.startsWith('dart-sdk/lib');
}

bool _discardBrowserStacktraceLine(String line) {
var match = _browserStackTraceRegex.matchAsPrefix(line);
if (match == null) {
return false;
}
return match.group(1)!.startsWith('package:logger') ||
match.group(1)!.startsWith('dart:');
}
}
Loading

0 comments on commit fb5e288

Please sign in to comment.