From 7d8c8ebfdc5ec0ad1e016178e6de0379662d241f Mon Sep 17 00:00:00 2001 From: Simon Leier Date: Tue, 4 Jun 2019 22:16:11 +0200 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 15 ++++ .metadata | 10 +++ .vscode/launch.json | 14 ++++ CHANGELOG.md | 2 + LICENSE | 21 +++++ README.md | 128 ++++++++++++++++++++++++++++ art/screenshot.png | Bin 0 -> 52894 bytes example/.gitignore | 1 + example/lib/main.dart | 29 +++++++ example/pubspec.yaml | 11 +++ lib/logger.dart | 8 ++ lib/src/logger.dart | 106 +++++++++++++++++++++++ lib/src/pretty_printer.dart | 162 ++++++++++++++++++++++++++++++++++++ pubspec.yaml | 14 ++++ 15 files changed, 523 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 .vscode/launch.json create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 art/screenshot.png create mode 100644 example/.gitignore create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.yaml create mode 100644 lib/logger.dart create mode 100644 lib/src/logger.dart create mode 100644 lib/src/pretty_printer.dart create mode 100644 pubspec.yaml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0ad5b066 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +.dart_tool/ +.idea/ +.iml + +.packages +.pub/ + +build/ +ios/ +android/ +demo/ + +pubspec.lock +doc/ \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 00000000..5ab1e9a7 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b + channel: stable + +project_type: package diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..a60a3010 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Example", + "request": "launch", + "type": "dart", + "program": "example/lib/main.dart" + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..07ce352e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.4.0 +- First version of the new logger \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4827a517 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Simon Leier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..eb765368 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# Logger + +[![Version](https://img.shields.io/pub/v/logger.svg)](https://pub.dartlang.org/packages/logger) ![Runtime](https://img.shields.io/badge/dart-%3E%3D2.1-brightgreen.svg) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat) + +Small, easy to use and extensible logger which prints beautiful logs. +Inspired by [logger](https://github.com/orhanobut/logger) for Android. + +**Show some ❤️ and star the repo to support the project** + +### Resources: +- [Documentation](https://pub.dartlang.org/documentation/logger/latest/logger/) +- [Pub Package](https://pub.dartlang.org/packages/logger) +- [GitHub Repository](https://github.com/leisim/logger) + +## Getting Started + +Just create an instance of `Logger` and start logging: +```dart +var logger = Logger(); + +logger.d("Logger is working!"); +``` + +Instead of a string message, you can also pass other objects like `List`, `Map` or `Set`. + +## Output + +![](https://raw.githubusercontent.com/leisim/logger/master/art/screenshot.png) + +## Log level + +You can log with different levels: + +```dart +logger.v("Verbose log"); + +logger.d("Debug log"); + +logger.i("Info log"); + +logger.w("Warning log"); + +logger.e("Error log"); + +logger.wtf("What a terrible failure log"); +``` + +To show only specific log levels, you can set: + +```dart +Logger.level = Level.warning; +``` + +This hides all `verbose`, `debug` and `info` log events. + +## Options + +When creating a logger, you can pass some options: + +```dart +var logger = Logger( + printer: PrettyPrinter(), // Use the PrettyPrinter to format and print log + filter: null, // Use the default LogFilter (-> only log in debug mode) +); +``` + +If you use the `PrettyPrinter`, there are more options: + +```dart +var logger = Logger( + printer: PrettyPrinter( + methodCount: 2, // number of method calls to be displayed + errorMethodCount: 8, // number of method calls if stacktrace is provided + lineLength : 120 // width of the output + ), +) +``` + +## LogPrinter + +You can implement your own `LogPrinter`. This gives you maximum flexibility. A very basic printer could look like this: + +``` +class MyPrinter extends LogPrinter { + @override + void log(Level level, dynamic message, dynamic error, StackTrace stackTrace) { + print(message); + } +} +``` + +If you created a cool `LogPrinter` which might be helpful to others, feel free to open a pull request. :) + +## LogFilter + +The `LogFilter` filters which logs should be shown and which don't. +The default implementation shows all logs with `level >= Logger.level` while in debug mode. In release mode all logs are omitted. + +You can create your own `LogFilter` like this: +```dart +var filter = (Level level, dynamic message, dynamic error, StackTrace stackTrace) { + return true; +} +``` +This will show all logs even in release mode. (**NOT** a good idea) + +## MIT License +``` +Copyright (c) 2019 Simon Leier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git a/art/screenshot.png b/art/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5ee5440421644b7f34a70e701f8aaddbea921edf GIT binary patch literal 52894 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 z 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 TJ$mK%4CT_*GSsY5fAZ>IY#9*7 literal 0 HcmV?d00001 diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 00000000..ee4c9268 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1 @@ +/test diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 00000000..0b37ad2d --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,29 @@ +import 'package:logger/logger.dart'; + +var logger = Logger(); + +var loggerNoStack = Logger(printer: PrettyPrinter()); + +void main() { + teesr(); +} + +void foo() { + logger.v("Log message with 2 methods"); + + logger.d("Log message with 2 methods", "MIST"); + + logger.i("Log message with 2 methods"); + + logger.w("Just a warning!"); + + logger.e("Error! Something bad happened", "PROB"); + + logger.wtf("Hello world.", "PROBLEM!", StackTrace.current); + + logger.d({"key": 5, "value": "something"}); +} + +void teesr() { + foo(); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 00000000..2220867f --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,11 @@ +name: example +description: Logger Example + +version: 1.0.0 + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + logger: + path: ../ \ No newline at end of file diff --git a/lib/logger.dart b/lib/logger.dart new file mode 100644 index 00000000..97bf4155 --- /dev/null +++ b/lib/logger.dart @@ -0,0 +1,8 @@ +/// Small, easy to use and extensible logger which prints beautiful logs. +library logger; + +import 'dart:convert'; +import 'package:ansicolor/ansicolor.dart'; + +part 'src/pretty_printer.dart'; +part 'src/logger.dart'; diff --git a/lib/src/logger.dart b/lib/src/logger.dart new file mode 100644 index 00000000..dcb8bfd1 --- /dev/null +++ b/lib/src/logger.dart @@ -0,0 +1,106 @@ +part of logger; + +/// [Level]s to control logging output. Logging can be enabled to include all +/// levels above certain [Level]. +enum Level { + verbose, + debug, + info, + warning, + error, + wtf, + nothing, +} + +/// `LogFilter` is called every time a new log message is sent and decides if +/// it will be printed or canceled. +/// +/// The default implementation is [Logger._defaultFilter]. +/// Every implementation should consider [Logger.level]. +typedef LogFilter = bool Function( + Level level, + dynamic message, [ + dynamic error, + StackTrace stackTrace, +]); + +/// An abstract handler of log messages. +/// +/// You can implement a `LogPrinter` from scratch or extend [PrettyPrinter]. +abstract class LogPrinter { + /// Is called by the Logger for each log message. + void log(Level level, dynamic message, dynamic error, StackTrace stackTrace); +} + +class Logger { + /// The current logging level of the app. + /// + /// All logs with levels below this level will be omitted. + static Level level = Level.verbose; + + final LogPrinter _printer; + final LogFilter _filter; + + /// Create a new Logger. + /// + /// You can provide a custom [printer] and [filter]. Otherwise the default + /// [PrettyPrinter] and [_defaultFilter] will be used. + Logger({LogPrinter printer, LogFilter filter}) + : _printer = printer ?? PrettyPrinter(), + _filter = filter ?? _defaultFilter; + + /// Log message at level [Level.verbose]. + void v(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.verbose, message, error, stackTrace); + } + + /// Log message at level [Level.debug]. + void d(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.debug, message, error, stackTrace); + } + + /// Log message at level [Level.info]. + void i(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.info, message, error, stackTrace); + } + + /// Log message at level [Level.warning]. + void w(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.warning, message, error, stackTrace); + } + + /// Log message at level [Level.error]. + void e(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.error, message, error, stackTrace); + } + + /// Log message at level [Level.wtf]. + void wtf(dynamic message, [dynamic error, StackTrace stackTrace]) { + log(Level.wtf, message, error, stackTrace); + } + + /// Log message with [level]. + void log(Level level, dynamic message, + [dynamic error, StackTrace stackTrace]) { + if (_filter(level, message, error, stackTrace)) { + _printer.log(level, message, error, stackTrace); + } + } + + /// Default implementation of [LogFilter]. All log + static bool _defaultFilter( + Level level, + dynamic message, [ + dynamic error, + StackTrace stackTrace, + ]) { + var shouldLog = false; + assert(() { + if (level.index >= Logger.level.index) { + shouldLog = true; + } + return true; + }()); + return shouldLog; + } +} diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart new file mode 100644 index 00000000..9aacea24 --- /dev/null +++ b/lib/src/pretty_printer.dart @@ -0,0 +1,162 @@ +part of logger; + +/// Default implementation of [LogPrinter] +/// +/// Outut looks like this: +/// +/// ┌────────────────────────── +/// │ Error info +/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ +/// │ Method stack history +/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ +/// │ Log message +/// └────────────────────────── +/// ``` +class PrettyPrinter extends LogPrinter { + static const topLeftCorner = '┌'; + static const bottomLeftCorner = '└'; + static const middleCorner = '├'; + static const verticalLine = '│'; + static const doubleDivider = "─"; + static const singleDivider = "┄"; + + static final colors = { + Level.verbose: AnsiPen()..gray(level: 0.5), + Level.debug: AnsiPen(), + Level.info: AnsiPen()..blue(bold: true), + Level.warning: AnsiPen()..xterm(208), + Level.error: AnsiPen()..xterm(196), + Level.wtf: AnsiPen()..xterm(199), + }; + + static final stackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)'); + static const String indent = ' '; + + final int methodCount; + final int errorMethodCount; + final int lineLength; + + String topBorder = ''; + String middleBorder = ''; + String bottomBorder = ''; + + PrettyPrinter({ + this.methodCount = 2, + this.errorMethodCount = 8, + this.lineLength = 120, + }) { + var doubleDividerLine = StringBuffer(); + var singleDividerLine = StringBuffer(); + for (int i = 0; i < lineLength - 1; i++) { + doubleDividerLine.write(doubleDivider); + singleDividerLine.write(singleDivider); + } + + topBorder = "$topLeftCorner$doubleDividerLine"; + middleBorder = "$middleCorner$singleDividerLine"; + bottomBorder = "$bottomLeftCorner$doubleDividerLine"; + } + + @override + void log(Level level, dynamic message, dynamic error, StackTrace stackTrace) { + var messageStr = stringifyMessage(message); + var stackTraceStr = collectStackTrace(stackTrace); + var errorStr = error?.toString(); + + var output = formatOutput(level, messageStr, errorStr, stackTraceStr); + for (var line in output) { + printLine(level, line); + } + } + + String collectStackTrace(StackTrace stackTrace) { + if (stackTrace == null) { + if (methodCount > 0) { + return formatStackTrace(StackTrace.current, methodCount); + } + } else if (errorMethodCount > 0) { + return formatStackTrace(stackTrace, errorMethodCount); + } + + return null; + } + + String formatStackTrace(StackTrace stackTrace, int methodCount) { + var lines = stackTrace.toString().split("\n"); + + var formatted = []; + var count = 0; + for (var line in lines) { + var match = stackTraceRegex.matchAsPrefix(line); + if (match != null) { + if (match.group(2).startsWith('package:logger')) { + continue; + } + var newLine = ("#${count + 1} ${match.group(1)} (${match.group(2)})"); + formatted.add(newLine.replaceAll('', '()')); + if (++count == methodCount) { + break; + } + } else if (line.startsWith('<')) { + formatted.add(line); + } + } + + if (formatted.isEmpty) { + return null; + } else { + return formatted.join('\n'); + } + } + + String stringifyMessage(dynamic message) { + if (message is Map || message is Iterable) { + var encoder = JsonEncoder.withIndent(indent); + return encoder.convert(message); + } else { + return message.toString(); + } + } + + List formatOutput( + Level level, String message, String error, String stacktrace) { + var pen = colors[level]; + List output = [pen(topBorder)]; + + if (error != null) { + AnsiPen errorPen; + if (level == Level.wtf) { + errorPen = AnsiPen()..xterm(199, bg: true); + } else { + errorPen = AnsiPen()..xterm(196, bg: true); + } + for (var line in error.split('\n')) { + output.add( + pen('$verticalLine ') + + resetForeground() + + errorPen(line) + + resetBackground(), + ); + } + output.add(pen(middleBorder)); + } + + if (stacktrace != null) { + for (var line in stacktrace.split('\n')) { + output.add('$pen$verticalLine $line'); + } + output.add(pen(middleBorder)); + } + + for (var line in message.split('\n')) { + output.add(pen('$verticalLine $line')); + } + output.add(pen(bottomBorder)); + + return output; + } + + void printLine(Level level, String line) { + print(line); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..c6ea3202 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: logger +description: Small, easy to use and extensible logger which prints beautiful logs. +version: 0.4.0 +author: Simon Leier +homepage: http://github.com/leisim/logger + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + ansicolor: ^1.0.2 + +dev_dependencies: + test: any \ No newline at end of file