Skip to content

Commit 95760e2

Browse files
authored
Merge pull request #4 from DutchCodingCompany/feature/bolt_logger
⚡ Bolt logger
2 parents 3e427b5 + f200b46 commit 95760e2

20 files changed

+889
-2
lines changed

docs/bolt_logger.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# ⚡️ BoltLogger!
2+
_**Super charge your logging and zap logs like Zeus with the BoltLogger.**_
3+
4+
## 🚀 Features
5+
- Built on top of the `logging` package. Meaning package that use the `logging` package will also be visible in the BoltLogger.
6+
- Easy to use and setup.
7+
- Supports multiple charges (log outputs).
8+
- Supports multiple log levels.
9+
- Has extension methods for easy logging, with include reference to the calling class.
10+
11+
## 🛠 Setup
12+
To use the `BoltLogger`, you need to charge the logger with a `BoltCharge`.
13+
14+
```dart
15+
BoltLogger.charge(DebugConsoleCharge());
16+
```
17+
18+
Available charges:
19+
- `DebugConsoleCharge`: Logs output to the console.
20+
- `FileCharge`: Logs output to a file.
21+
- `MemoryCharge`: Keeps track of logs in memory.
22+
23+
## 📝 Zapping! (Usage)
24+
The `BoltLogger` aims to be easy to use. Once `BoltLogger` is charged you can start zapping logs! By zapping we mean literally calling `zap`.
25+
26+
Logging a message in `BoltLogger` is as simple as calling the `zap` method.
27+
28+
```dart
29+
class MyAwesomeClass{
30+
void doSomething() {
31+
zap('This is a message');
32+
}
33+
}
34+
```
35+
36+
`BoltLogger` offer `zap` as a extension on `Object` meaning you can call `zap` on or in any object.
37+
Because it's an extension we can automatically include the `runtimeType` of the object that called `zap` as tag for the log message.
38+
39+
Just as Zeus when zapping your not only limited to only zap `String`s, you can zap any object. For example: Exceptions, Errors, StackTrace. BoltLogger will take care of the objects accordingly.
40+
41+
Beside zapping messages you can also provide a custom `tag` and `level` to the `zap` method.
42+
43+
`BoltLogger` also offers a `shock` to zap logs with at a `Level.SERVERE` level.
44+
45+
If the extension methods are not available you can call `BoltLogger.zap` directly.
46+
47+
## 📦 Example
48+
```dart
49+
50+
class MyAwesomeClass{
51+
void doSomething() {
52+
zap('This is a message');
53+
}
54+
55+
void doSomethingElse() {
56+
shock(Exception('Shocking!'));
57+
}
58+
}
59+
60+
void main() {
61+
BoltLogger.charge(DebugConsoleCharge());
62+
63+
final myAwesomeClass = MyAwesomeClass();
64+
myAwesomeClass.doSomething();
65+
myAwesomeClass.doSomethingElse();
66+
}
67+
```

lib/common/dcc_logger.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import 'package:intl/intl.dart';
33
import 'package:logging/logging.dart';
44

55
/// A logger that can be used to log messages.
6+
@Deprecated('Use BoltLogger instead')
67
class DCCLogger {
78
static Logger? _logger;
89
static final DateFormat _formatter = DateFormat(DateFormat.HOUR24_MINUTE);
910

1011
/// Initialise the logger. This needs to be called before using the logger.
1112
/// Using this is done in the main.dart file.
13+
@Deprecated('Use BoltLogger.charge instead')
1214
static void initialise([
1315
String? name,
1416
String? tag,
@@ -65,6 +67,7 @@ class DCCLogger {
6567
/// DCCLogger.info('test bericht', tag: 'test');
6668
/// ```
6769
/// prints: [[13:46]] [[INFO]] DCC: <test> test bericht
70+
@Deprecated('Use BoltLogger.zap instead')
6871
static void info(
6972
String message, {
7073
Object? error,
@@ -82,6 +85,7 @@ class DCCLogger {
8285
/// DCCLogger.severe('test bericht', tag: 'test');
8386
/// ```
8487
/// prints: [[13:46]] [[SEVERE]] DCC: <test> test bericht
88+
@Deprecated('Use BoltLogger.shock instead')
8589
static void severe(
8690
String message, {
8791
Object? error,

lib/dcc_toolkit.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
library dcc_toolkit;
33

44
export 'chopper/chopper.dart';
5-
export 'common/dcc_logger.dart';
65
export 'common/dimensions.dart';
76
export 'common/extensions/build_context.dart';
87
export 'common/extensions/color.dart';
98
export 'common/result/result.dart';
9+
export 'logger/bolt_logger.dart';

lib/logger/bolt_logger.dart

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import 'dart:async';
2+
3+
import 'package:dcc_toolkit/logger/charges/bolt_charge.dart';
4+
import 'package:dcc_toolkit/logger/zap_event.dart';
5+
import 'package:logging/logging.dart';
6+
7+
export 'package:dcc_toolkit/logger/charges/bolt_charge.dart';
8+
export 'package:dcc_toolkit/logger/extensions/stacktrace.dart';
9+
export 'package:dcc_toolkit/logger/extensions/zap_extension.dart';
10+
export 'package:dcc_toolkit/logger/ui/bolt_logger_view.dart';
11+
export 'package:dcc_toolkit/logger/zap_event.dart';
12+
13+
/// {@template bolt_logger}
14+
/// Super charge your logging and zap logs like Zeus with the BoltLogger.
15+
/// {@endtemplate}
16+
class BoltLogger {
17+
/// {@macro bolt_logger}
18+
factory BoltLogger() => _instance;
19+
20+
BoltLogger._();
21+
22+
static final _instance = BoltLogger._();
23+
24+
// Is cancelled in discharge
25+
//ignore: cancel_subscriptions
26+
StreamSubscription<LogRecord>? _subscription;
27+
28+
final Map<String, BoltCharge> _outputs = {};
29+
30+
/// Add [BoltCharge] charges to the logger. This will start the logger if it is not already running.
31+
/// Without any charges, the logger will not output any logs.
32+
///
33+
/// Available charges:
34+
/// - [DebugConsoleCharge]
35+
/// - [MemoryCharge]
36+
/// - [FileCharge]
37+
///
38+
/// For example to add a [DebugConsoleCharge] charge, use the following code snippet:
39+
/// ```dart
40+
/// BoltLogger.charge([DebugConsoleCharge()]);
41+
/// ```
42+
static void charge(List<BoltCharge> charges) {
43+
for (final charge in charges) {
44+
_instance._outputs.putIfAbsent(charge.name, () => charge);
45+
}
46+
_instance._subscribeIfNeeded();
47+
}
48+
49+
/// Look up registered charge by name.
50+
static BoltCharge? getCharge(String name) {
51+
return _instance._outputs[name];
52+
}
53+
54+
void _subscribeIfNeeded() {
55+
Logger.root.level = Level.ALL;
56+
_subscription ??= Logger.root.onRecord.listen((record) {
57+
for (final output in _outputs.values) {
58+
output.logOutput(ZapEvent.fromRecord(record));
59+
}
60+
});
61+
}
62+
63+
/// Discharge the logger and all charges.
64+
static void discharge() {
65+
_instance._subscription?.cancel();
66+
_instance._subscription = null;
67+
for (final output in _instance._outputs.values) {
68+
output.discharge();
69+
}
70+
_instance._outputs.clear();
71+
}
72+
73+
/// {@template zap}
74+
/// Zap a log message with optional [tag] and [level].
75+
///
76+
/// For example to zap a message:
77+
/// ```dart
78+
/// BoltLogger.zap('Electricity is in the air!');
79+
/// ```
80+
///
81+
/// The [message] can also be an [Exception]/[Error], [StackTrace] or a [List] to zap all 3 types ([Object]?, [Exception]/[Error], [StackTrace]). These will be passed to the logger.
82+
///
83+
/// ```dart
84+
/// BoltLogger.zap(['Electricity is in the air!', myException, stackTrace]);
85+
/// ```
86+
///
87+
/// {@endtemplate}
88+
static void zap(
89+
Object? message, {
90+
String? tag,
91+
Level level = Level.INFO,
92+
}) {
93+
Object? msg;
94+
Object? error;
95+
StackTrace? stacktrace;
96+
97+
void zapMap(Object? value) {
98+
if (value is Exception || value is Error && error == null) {
99+
error = value;
100+
} else if (value is StackTrace && stacktrace == null) {
101+
stacktrace = value;
102+
} else if (msg == null) {
103+
msg = value;
104+
} else {
105+
final errorMessage =
106+
'When zapping a list it can only contain one of each Exception/Error, StackTrace or Object?: $message';
107+
assert(false, errorMessage);
108+
error = AssertionError([errorMessage]);
109+
msg = '';
110+
stacktrace = StackTrace.empty;
111+
}
112+
}
113+
114+
if (message is List) {
115+
for (final Object? value in message) {
116+
zapMap(value);
117+
}
118+
} else {
119+
zapMap(message);
120+
}
121+
122+
Logger(tag ?? 'BoltLogger').log(level, msg, error, stacktrace);
123+
}
124+
125+
/// {@template shock}
126+
/// Shock is a zap intensified! It zaps a log message default [level] of [Level.SEVERE].
127+
///
128+
/// {@macro zap}
129+
///
130+
/// {@endtemplate}
131+
static void shock(
132+
Object? message, {
133+
String? tag,
134+
Level level = Level.SEVERE,
135+
}) {
136+
zap(message, tag: tag, level: level);
137+
}
138+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:dcc_toolkit/logger/bolt_logger.dart';
2+
3+
export 'package:dcc_toolkit/logger/charges/debug_console_charge.dart';
4+
export 'package:dcc_toolkit/logger/charges/file_charge.dart';
5+
export 'package:dcc_toolkit/logger/charges/memory_charge.dart';
6+
7+
/// Interface for creating [BoltCharge]s to power the [BoltLogger]
8+
abstract interface class BoltCharge {
9+
/// The name of the charge.
10+
abstract final String name;
11+
12+
/// Log the output of the [ZapEvent].
13+
void logOutput(ZapEvent event);
14+
15+
/// Discharge the charge.
16+
void discharge();
17+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:dcc_toolkit/logger/charges/bolt_charge.dart';
2+
import 'package:dcc_toolkit/logger/util/ansi_support.dart'
3+
if (dart.library.io) 'package:dcc_toolkit/logger/util/ansi_support_io.dart'
4+
if (dart.library.html) 'package:dcc_toolkit/logger/util/ansi_support_web.dart';
5+
import 'package:dcc_toolkit/logger/zap_event.dart';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:logging/logging.dart';
8+
9+
/// {@template debug_console_charge}
10+
/// A [BoltCharge] that logs output to the console with ANSI color support.
11+
/// {@endtemplate}
12+
class DebugConsoleCharge implements BoltCharge {
13+
/// {@macro debug_console_charge}
14+
const DebugConsoleCharge();
15+
@override
16+
String get name => 'DebugConsoleCharge';
17+
18+
@override
19+
void logOutput(ZapEvent event) {
20+
if (!kDebugMode) return;
21+
_paintLines(event).forEach(debugPrint);
22+
}
23+
24+
List<String> _paintLines(ZapEvent event) {
25+
final shouldPaint = supportsAnsiEscapes &&
26+
(event.origin.level.value >= Level.SEVERE.value ||
27+
event.origin.stackTrace != null ||
28+
event.origin.error != null);
29+
30+
return shouldPaint
31+
? event.lines.map((line) => '$_red$line$_reset').toList()
32+
: event.lines;
33+
}
34+
35+
@override
36+
void discharge() {}
37+
}
38+
39+
const _esc = '\x1B[';
40+
const _reset = '${_esc}0m';
41+
const _red = '${_esc}38;5;1m';
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:dcc_toolkit/logger/charges/bolt_charge.dart';
5+
import 'package:dcc_toolkit/logger/zap_event.dart';
6+
import 'package:intl/intl.dart';
7+
8+
/// {@template file_charge}
9+
/// A [BoltCharge] that logs output to a file.
10+
///
11+
/// The [FileCharge] will write logs to a file in the specified [path].
12+
/// Logs are written to the file in batches, this is done when it reaches the [bufferSize] or every [writeDelay].
13+
///
14+
/// {@endtemplate}
15+
class FileCharge implements BoltCharge {
16+
/// {@macro file_charge}
17+
FileCharge(
18+
this.path, {
19+
this.bufferSize = 1000,
20+
this.writeDelay = const Duration(seconds: 5),
21+
}) {
22+
final fileName = '${DateFormat('yyyy-MM-dd').format(DateTime.now())}.log';
23+
_file = File('$path/$fileName');
24+
25+
Timer.periodic(writeDelay, (_) => _flush());
26+
}
27+
@override
28+
String get name => 'FileCharge';
29+
30+
/// The size of the buffer (in lines) before writing to the file.
31+
final int bufferSize;
32+
33+
/// The path to the directory where the log files will be written.
34+
final String path;
35+
36+
/// The delay between writing to the file.
37+
final Duration writeDelay;
38+
File? _file;
39+
final List<ZapEvent> _buffer = [];
40+
IOSink? _sink;
41+
Timer? _timer;
42+
43+
@override
44+
void logOutput(ZapEvent event) {
45+
_buffer.add(event);
46+
47+
if (_buffer.length >= bufferSize) {
48+
_flush();
49+
}
50+
}
51+
52+
void _flush() {
53+
if (_buffer.isEmpty) return;
54+
55+
_sink ??= _file?.openWrite(mode: FileMode.append);
56+
57+
for (final event in _buffer) {
58+
_sink?.writeAll(event.lines, '\n');
59+
_sink?.writeln();
60+
}
61+
_buffer.clear();
62+
}
63+
64+
@override
65+
void discharge() {
66+
_timer?.cancel();
67+
_timer = null;
68+
_flush();
69+
_sink?.close();
70+
_sink = null;
71+
}
72+
}

0 commit comments

Comments
 (0)