Skip to content

Commit

Permalink
#1 implement roggle
Browse files Browse the repository at this point in the history
  • Loading branch information
susatthi committed Apr 29, 2022
1 parent ef016a7 commit 82cae23
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 76 deletions.
6 changes: 6 additions & 0 deletions .run/main.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="example/main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application">
<option name="filePath" value="$PROJECT_DIR$/example/main.dart" />
<method v="2" />
</configuration>
</component>
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
## 0.0.1
## 0.1.0

* TODO: Describe initial release.
- First version
16 changes: 2 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
# roggle

For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->

TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
Simple, colorful and easy to expand logger for dart.

## Features

Expand Down
10 changes: 7 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
include: package:flutter_lints/flutter.yaml
# https://pub.dev/packages/pedantic_mono
include: package:pedantic_mono/analysis_options.yaml

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
linter:
rules:
avoid_classes_with_only_static_members: false
constant_identifier_names: true
prefer_relative_imports: true
22 changes: 22 additions & 0 deletions example/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:roggle/roggle.dart';

final logger = Roggle(
printer: SinglePrettyPrinter(
loggerName: '[APP]',
stackTraceLevel: Level.error,
),
);

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

void demo() {
logger.d('Log message with 2 methods');

logger.e('Error! Something bad happened', 'Test Error');

logger.i('Log message');
}
9 changes: 4 additions & 5 deletions lib/roggle.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
library roggle;

/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}
export 'package:logger/logger.dart' hide Logger, LogCallback, OutputCallback;

export 'src/printers/single_pretty_printer.dart';
export 'src/roggle.dart';
302 changes: 302 additions & 0 deletions lib/src/printers/single_pretty_printer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import 'package:logger/logger.dart';
import 'package:stack_trace/stack_trace.dart';

/// Default implementation of [LogPrinter].
///
/// Output looks like this:
/// ```
/// 💡 [INFO] 06:46:15.354 demo (file:///your/file/path/roggle/example/main.dart:16:10): Log message
/// ```
class SinglePrettyPrinter extends LogPrinter {
SinglePrettyPrinter({
this.loggerName,
this.colors = true,
this.printCaller = true,
this.printEmoji = true,
this.printLevel = true,
this.printTime = true,
this.stackTraceLevel = Level.nothing,
this.stackTraceMethodCount = 20,
this.stackTraceFilters = const [],
this.stackTracePrefix = _defaultStackTracePrefix,
Map<Level, AnsiColor>? levelColors,
this.levelEmojis = _defaultLevelEmojis,
this.levelLabels = _defaultLevelLabels,
}) : _levelColors = levelColors ?? _defaultLevelColors;

/// If specified, it will be output at the beginning of the log.
final String? loggerName;

/// If set to true, the log will be colorful.
final bool colors;

/// If set to true, caller will be output to the log.
final bool printCaller;

/// If set to true, the emoji will be output to the log.
final bool printEmoji;

/// If set to true, the log level string will be output to the log.
final bool printLevel;

/// If set to true, the time stamp will be output to the log.
final bool printTime;

/// The current logging level to display stack trace.
///
/// All stack traces with levels below this level will be omitted.
final Level stackTraceLevel;

/// Number of stack trace methods to display.
final int stackTraceMethodCount;

/// No stack trace that matches the regular expression is output.
final List<RegExp> stackTraceFilters;

/// Stack trace prefix.
final String stackTracePrefix;

/// Color for each log level.
final Map<Level, AnsiColor> _levelColors;

/// Emoji for each log level.
final Map<Level, String> levelEmojis;

/// String for each log level.
final Map<Level, String> levelLabels;

/// Path to this file.
static final _selfPath = _getSelfPath();

/// Stack trace prefix default.
static const _defaultStackTracePrefix = '│';

/// Color default for each log level.
static final _defaultLevelColors = {
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),
};

/// Emoji default for each log level.
static const _defaultLevelEmojis = {
Level.verbose: '🐱',
Level.debug: '🐛',
Level.info: '💡',
Level.warning: '⚠️',
Level.error: '⛔',
Level.wtf: '👾',
};

/// String default for each log level.
static const _defaultLevelLabels = {
Level.verbose: '[VERBOSE]',
Level.debug: '[DEBUG] ',
Level.info: '[INFO] ',
Level.warning: '[WARNING]',
Level.error: '[ERROR] ',
Level.wtf: '[WTF] ',
};

/// 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]+\/)');

/// Returns the path to this file.
static String _getSelfPath() {
final match = RegExp(r'^(.+.dart)').firstMatch(Frame.caller(0).toString());
if (match == null) {
return '';
}
return match.group(1)!;
}

@override
List<String> log(LogEvent event) {
List<String>? stackTraceLines;
if (event.stackTrace != null) {
// If stackTrace is not null, it will be displayed with priority.
stackTraceLines = _getStackTrace(stackTrace: event.stackTrace);
} else if (event.level.index >= stackTraceLevel.index) {
stackTraceLines = _getStackTrace();
}

return _formatMessage(
level: event.level,
message: _stringifyMessage(event.message),
error: event.error?.toString(),
stackTrace: stackTraceLines,
);
}

String? _getCaller() {
final lines = StackTrace.current.toString().split('\n');
for (final line in lines) {
if (_discardDeviceStackTraceLine(line) ||
_discardWebStackTraceLine(line) ||
_discardUserStacktraceLine(line) ||
line.isEmpty) {
continue;
}

// Remove unnecessary parts.
if (_deviceStackTraceRegex.matchAsPrefix(line) != null) {
return line
.replaceFirst(RegExp(r'#\d+\s+'), '')
.replaceFirst(RegExp(r'package:[a-z0-9_]+\/'), '/');
}
if (_webStackTraceRegex.matchAsPrefix(line) != null) {
return line.replaceFirst(RegExp(r'^packages\/[a-z0-9_]+\/'), '/');
}
}
return null;
}

List<String> _getStackTrace({
StackTrace? stackTrace,
}) {
final lines = (stackTrace ?? StackTrace.current).toString().split('\n');
final formatted = <String>[];
var count = 0;
for (final line in lines) {
if (_discardDeviceStackTraceLine(line) ||
_discardWebStackTraceLine(line) ||
_discardUserStacktraceLine(line) ||
line.isEmpty) {
continue;
}
final replaced = line.replaceFirst(RegExp(r'#\d+\s+'), '');
formatted.add('$stackTracePrefix #$count $replaced');
if (++count == stackTraceMethodCount) {
break;
}
}
return formatted;
}

bool _discardDeviceStackTraceLine(String line) {
final match = _deviceStackTraceRegex.matchAsPrefix(line);
if (match == null) {
return false;
}
return match.group(2)!.startsWith('package:roggle') ||
line.contains(_selfPath);
}

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

bool _discardUserStacktraceLine(String line) =>
stackTraceFilters.any((element) => element.hasMatch(line));

String _getCurrentTime() {
String _threeDigits(int n) {
if (n >= 100) {
return '$n';
}
if (n >= 10) {
return '0$n';
}
return '00$n';
}

String _twoDigits(int n) {
if (n >= 10) {
return '$n';
}
return '0$n';
}

final now = DateTime.now();
final h = _twoDigits(now.hour);
final min = _twoDigits(now.minute);
final sec = _twoDigits(now.second);
final ms = _threeDigits(now.millisecond);
return '$h:$min:$sec.$ms';
}

String _stringifyMessage(dynamic message) {
if (message is dynamic Function()) {
return message().toString();
} else if (message is String) {
return message;
}
return message.toString();
}

AnsiColor _getLevelColor(Level level) {
if (colors) {
return _levelColors[level]!;
} else {
return AnsiColor.none();
}
}

List<String> _formatMessage({
required Level level,
required String message,
String? error,
List<String>? stackTrace,
}) {
final color = _getLevelColor(level);
final fixed = _formatFixed(level: level);
final logs = <String>[
color('$fixed$message'),
];

if (error != null) {
logs.add(color('$fixed$stackTracePrefix $error'));
}

if (stackTrace != null && stackTrace.isNotEmpty) {
for (final line in stackTrace) {
logs.add(color('$fixed$line'));
}
}
return logs;
}

String _formatFixed({
required Level level,
}) {
final buffer = <String>[];

if (printEmoji) {
buffer.add(levelEmojis[level]!);
}
if (loggerName != null) {
buffer.add(loggerName!);
}
if (printLevel) {
buffer.add(levelLabels[level]!);
}
if (printTime) {
buffer.add(_getCurrentTime());
}
if (printCaller) {
final caller = _getCaller();
if (caller != null) {
buffer.add(caller);
}
}
return buffer.isNotEmpty ? '${buffer.join(' ')}: ' : '';
}
}
Loading

0 comments on commit 82cae23

Please sign in to comment.