Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased
* Added `BoltLogger.surge` and the `surge` extension method for logging at `Level.WARNING`, completing the `zap` (info) / `surge` (warning) / `shock` (severe) trio
* `DebugConsoleCharge` now prints warning level logs in yellow; severe logs and logs carrying an error or stack trace remain red
* `JsonSerializableConverter` now uses `BoltLogger.surge` for `JsonConverterException` logging

## 0.0.21
* Changed `NoContent` to a typedef for `dynamic` to fix chopper integration with empty response bodies
* `JsonSerializableConverter` now throws a `JsonConverterException` instead of a `JsonUnsupportedObjectError`, so it can be caught by `tryCall`
Expand Down
21 changes: 18 additions & 3 deletions doc/bolt_logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,19 @@ Just as Zeus when zapping your not only limited to only zap `String`s, you can z

Beside zapping messages you can also provide a custom `tag` and `level` to the `zap` method.

`BoltLogger` also offers a `shock` to zap logs with at a `Level.SERVERE` level.
Next to `zap` there are two intensities available:
- `surge`: zaps a log at `Level.WARNING`, for things that are off but not fatal.
- `shock`: a zap intensified, zaps a log at `Level.SEVERE`.

If the extension methods are not available you can call `BoltLogger.zap` directly.
Both take the same arguments as `zap`, so you can pass a message, an `Exception`/`Error`, a `StackTrace` or a `List` combining them.

If the extension methods are not available you can call `BoltLogger.zap`, `BoltLogger.surge` or `BoltLogger.shock` directly.

## 🎨 Colors in the console
The `DebugConsoleCharge` colors its output when the terminal supports ANSI escapes:
- 🔴 Red: `Level.SEVERE` and up, or any log that carries an error or stack trace.
- 🟡 Yellow: `Level.WARNING` logs without an error or stack trace (a `surge`).
- ⚪️ Default: everything else.

## 📦 Example
```dart
Expand All @@ -51,7 +61,11 @@ class MyAwesomeClass{
void doSomething() {
zap('This is a message');
}


void doSomethingRisky() {
surge('This is not looking good...');
}

void doSomethingElse() {
shock(Exception('Shocking!'));
}
Expand All @@ -62,6 +76,7 @@ void main() {

final myAwesomeClass = MyAwesomeClass();
myAwesomeClass.doSomething();
myAwesomeClass.doSomethingRisky();
myAwesomeClass.doSomethingElse();
}
```
5 changes: 2 additions & 3 deletions lib/chopper/json_serializable_converter.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'dart:async';

import 'package:chopper/chopper.dart' hide Level;
import 'package:chopper/chopper.dart';
import 'package:dcc_toolkit/chopper/json_converter_exception.dart';
import 'package:dcc_toolkit/logger/bolt_logger.dart';
import 'package:json_annotation/json_annotation.dart' show CheckedFromJsonException;
import 'package:logging/logging.dart';

/// Method signature for a function that creates a dart object from a json map.
typedef JsonFactory<T> = T Function(Map<String, dynamic> json);
Expand All @@ -27,7 +26,7 @@ class JsonSerializableConverter extends JsonConverter {

Never _logAndThrow<T>(String message) {
final exception = JsonConverterException(T, message: message);
BoltLogger.zap(exception, tag: '$T', level: Level.WARNING);
BoltLogger.surge(exception, tag: '$T');

throw exception;
}
Expand Down
10 changes: 10 additions & 0 deletions lib/logger/bolt_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ class BoltLogger {
Logger(tag ?? 'BoltLogger').log(level, msg, error, stacktrace);
}

/// {@template surge}
/// Surge is a zap with a warning! It zaps a log message with a default [level] of [Level.WARNING].
///
/// {@macro zap}
///
/// {@endtemplate}
static void surge(Object? message, {String? tag, Level level = Level.WARNING}) {
zap(message, tag: tag, level: level);
}

/// {@template shock}
/// Shock is a zap intensified! It zaps a log message default [level] of [Level.SEVERE].
///
Expand Down
22 changes: 16 additions & 6 deletions lib/logger/charges/debug_console_charge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@ class DebugConsoleCharge implements BoltCharge {
}

List<String> _paintLines(ZapEvent event) {
final shouldPaint =
supportsAnsiEscapes &&
(event.origin.level.value >= Level.SEVERE.value ||
event.origin.stackTrace != null ||
event.origin.error != null);
if (!supportsAnsiEscapes) return event.lines;

return shouldPaint ? event.lines.map((line) => '$_red$line$_reset').toList() : event.lines;
final color = _colorFor(event);
if (color == null) return event.lines;

return event.lines.map((line) => '$color$line$_reset').toList();
}

String? _colorFor(ZapEvent event) {
if (event.origin.level.value >= Level.SEVERE.value ||
event.origin.stackTrace != null ||
event.origin.error != null) {
return _red;
}
if (event.origin.level.value >= Level.WARNING.value) return _yellow;
return null;
}

@override
Expand All @@ -38,3 +47,4 @@ class DebugConsoleCharge implements BoltCharge {
const _esc = '\x1B[';
const _reset = '${_esc}0m';
const _red = '${_esc}38;5;1m';
const _yellow = '${_esc}38;5;3m';
7 changes: 7 additions & 0 deletions lib/logger/extensions/zap_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ extension ZapExtension on Object {
BoltLogger.zap(message, tag: tag ?? runtimeType.toString(), level: level);
}

/// {@macro surge}
void surge(Object? message, {String? tag, Level level = Level.WARNING}) {
// We actually want to know to runtimeType of the object
//ignore: no_runtimeType_toString
BoltLogger.surge(message, tag: tag ?? runtimeType.toString(), level: level);
}

/// {@macro shock}
void shock(Object? message, {String? tag, Level level = Level.SEVERE}) {
// We actually want to know to runtimeType of the object
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: dcc-create-blocful-page
name: dcc-toolkit-create-blocful-page
description: Create a full page using BlocfulWidget with BlocPresentationMixin for one-shot events, Cubit state management, and native dialogs. Use when creating a new screen with BLoC, adding presentation events, wiring up a Cubit to a page, or showing platform-adaptive dialogs.
metadata:
last_modified: 2025-06-18
last_modified: "2025-06-18"
---

# Create a BlocfulWidget Page
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: dcc-create-paginated-cubit
name: dcc-toolkit-create-paginated-cubit
description: Scaffold a paginated Cubit using PaginationMixin and PaginationState with PaginatedScrollView and PaginationStateView widgets. Use when adding pagination, implementing infinite scroll, loading more items on scroll, or creating a paginated list.
metadata:
last_modified: 2025-06-18
last_modified: "2025-06-18"
---

# Create a Paginated Cubit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: dcc-setup-bolt-logger
name: dcc-toolkit-setup-bolt-logger
description: Set up and configure BoltLogger for structured logging with charges (DebugConsole, File, Memory). Use when adding logging, setting up error tracking, adding an in-app log viewer, or bootstrapping a Flutter app with error handling.
metadata:
last_modified: 2025-06-18
last_modified: "2025-06-18"
---

# Set Up BoltLogger
Expand All @@ -21,10 +21,10 @@ metadata:

BoltLogger is the DCC toolkit's structured logging system built on top of the `logging` package. It uses a "charge" architecture where output backends (charges) can be plugged in independently. The system supports:

- **DebugConsoleCharge** -- prints to the debug console with ANSI color for errors (only in debug mode)
- **DebugConsoleCharge** -- prints to the debug console with ANSI color for warnings and errors (only in debug mode)
- **FileCharge** -- writes logs to a file with buffering and periodic flushing
- **MemoryCharge** -- stores logs in memory for display via `BoltLoggerView`
- **ZapExtension** -- adds `zap()` and `shock()` methods to any object
- **ZapExtension** -- adds `zap()`, `surge()` and `shock()` methods to any object
- **runAppBootstrap()** -- wraps your app in error handling zones that auto-log with BoltLogger

## Prerequisites
Expand All @@ -45,20 +45,21 @@ import 'package:dcc_toolkit/common/run_app_bootstrap.dart';

| Class | Role |
|-------|------|
| `BoltLogger` | Singleton logger. Static methods: `charge()`, `zap()`, `shock()`, `discharge()`, `getCharge()` |
| `BoltLogger` | Singleton logger. Static methods: `charge()`, `zap()`, `surge()`, `shock()`, `discharge()`, `getCharge()` |
| `BoltCharge` | Interface for log output backends. Requires `name`, `logOutput(ZapEvent)`, `discharge()` |
| `DebugConsoleCharge` | Prints logs via `debugPrint`. ANSI red for errors. Only active in `kDebugMode` |
| `DebugConsoleCharge` | Prints logs via `debugPrint`. ANSI red for errors, yellow for warnings. Only active in `kDebugMode` |
| `FileCharge` | Writes to `{path}/{yyyy-MM-dd}.log`. Buffers up to `bufferSize` lines, flushes every `writeDelay` |
| `MemoryCharge` | Stores up to `maxItems` events in memory. Exposes `stream` and `items` for UI display |
| `ZapEvent` | Wraps a `LogRecord` with pre-formatted `lines` (List<String>) |
| `ZapExtension` | Extension on `Object` adding `zap()` and `shock()` using `runtimeType` as tag |
| `ZapExtension` | Extension on `Object` adding `zap()`, `surge()` and `shock()` using `runtimeType` as tag |
| `ZapStackTraceExtension` | Extension on `StackTrace` with `strike` getter for cleaned formatting |
| `BoltLoggerView` | Widget that renders in-app logs from a `MemoryCharge` via `StreamBuilder` + `ListView` |
| `runAppBootstrap()` | Runs app inside `runZonedGuarded` with `FlutterError.onError`, defaults to `BoltLogger.shock()` |

### Log Levels

- `BoltLogger.zap(message)` -- logs at `Level.INFO` (general information)
- `BoltLogger.surge(message)` -- logs at `Level.WARNING` (unexpected but recoverable situations)
- `BoltLogger.shock(message)` -- logs at `Level.SEVERE` (errors, exceptions)

### Message Types
Expand All @@ -74,7 +75,7 @@ The `message` parameter accepts:
**Task Progress:**
- [ ] 1. Set up `runAppBootstrap()` in `main.dart`
- [ ] 2. Configure charges based on environment
- [ ] 3. Add logging calls (`zap`/`shock`) to business logic
- [ ] 3. Add logging calls (`zap`/`surge`/`shock`) to business logic
- [ ] 4. (Optional) Add `BoltLoggerView` for in-app log viewing
- [ ] 5. Verify logs appear in console/file/viewer

Expand Down Expand Up @@ -161,6 +162,7 @@ BoltLogger.charge([
**Using static methods (anywhere):**
```dart
BoltLogger.zap('User logged in', tag: 'AuthService');
BoltLogger.surge('Token expires in 60s', tag: 'AuthService');
BoltLogger.shock(['Payment failed', exception, stackTrace], tag: 'PaymentService');
```

Expand All @@ -171,6 +173,7 @@ class UserRepository {
zap('Fetching user...'); // tag = 'UserRepository' (from runtimeType)
try {
// ...
surge('Cache miss, falling back to network'); // tag = 'UserRepository'
} catch (e, s) {
shock([e, s]); // tag = 'UserRepository'
}
Expand Down Expand Up @@ -314,4 +317,4 @@ After implementing:
2. Run the app in debug mode -- confirm logs appear in the debug console with the `⚡[HH:mm] I/Tag: message` format.
3. If using `FileCharge` -- verify the log file is created at the expected path.
4. If using `BoltLoggerView` -- navigate to the debug page and confirm logs are streaming.
5. Trigger an error -- confirm `shock()` messages appear in red (ANSI terminals) and include error + stack trace.
5. Trigger an error -- confirm `shock()` messages appear in red (ANSI terminals) and include error + stack trace. A plain `surge()` appears in yellow.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: dcc-setup-kleurplaat-boekwerk
name: dcc-toolkit-setup-kleurplaat-boekwerk
description: Create and configure a custom design system using KatjasKleurplaat (colors) and KatjasBoekwerk (typography) theme extensions. Use when setting up theming, adding a color palette, configuring typography, or integrating the DCC design system into a Flutter app.
metadata:
last_modified: 2025-06-18
last_modified: "2025-06-18"
---

# Set Up Kleurplaat & Boekwerk Design System
Expand Down
33 changes: 32 additions & 1 deletion test/logger/bolt_logger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,45 @@ void main() {
if (useBoltLogger) {
BoltLogger.zap('zap', level: level);
BoltLogger.zap('shock', level: level);
BoltLogger.surge('surge', level: level);
} else {
TestReferenceClass().zapExtension('zap', level: level);
TestReferenceClass().shockExtension('shock', level: level);
TestReferenceClass().surgeExtension('surge', level: level);
}

expect(memoryCharge.items.length, 2);
expect(memoryCharge.items.length, 3);
expect(memoryCharge.items[0].origin.level, level);
expect(memoryCharge.items[1].origin.level, level);
expect(memoryCharge.items[2].origin.level, level);
},
);

test('surge sends a message at Level.WARNING via a ZapEvent to a charge', () {
if (useBoltLogger) {
BoltLogger.surge('surge');
} else {
TestReferenceClass().surgeExtension('surge');
}

expect(memoryCharge.items.length, 1);
expect(memoryCharge.items[0].origin.message, 'surge');
expect(memoryCharge.items[0].origin.level, Level.WARNING);
expect(memoryCharge.items[0].origin.loggerName, useBoltLogger ? 'BoltLogger' : 'TestReferenceClass');
});

test('surge sends a tag via a ZapEvent to a charge', () {
if (useBoltLogger) {
BoltLogger.surge('surge', tag: 'tag1');
} else {
TestReferenceClass().surgeExtension('surge', tag: 'tag1');
}

expect(memoryCharge.items.length, 1);
expect(memoryCharge.items[0].origin.loggerName, 'tag1');
expect(memoryCharge.items[0].origin.level, Level.WARNING);
});

test('zap/shock sends Exception via a ZapEvent to a Charge', () {
final exception = Exception('exception');
if (useBoltLogger) {
Expand Down Expand Up @@ -258,4 +286,7 @@ class TestReferenceClass {

void shockExtension(Object? message, {String? tag, Level level = Level.SEVERE}) =>
shock(message, tag: tag, level: level);

void surgeExtension(Object? message, {String? tag, Level level = Level.WARNING}) =>
surge(message, tag: tag, level: level);
}
Loading