diff --git a/CHANGELOG.md b/CHANGELOG.md index 19eb52b..20086f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/doc/bolt_logger.md b/doc/bolt_logger.md index a07dc93..8b45257 100644 --- a/doc/bolt_logger.md +++ b/doc/bolt_logger.md @@ -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 @@ -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!')); } @@ -62,6 +76,7 @@ void main() { final myAwesomeClass = MyAwesomeClass(); myAwesomeClass.doSomething(); + myAwesomeClass.doSomethingRisky(); myAwesomeClass.doSomethingElse(); } ``` \ No newline at end of file diff --git a/lib/chopper/json_serializable_converter.dart b/lib/chopper/json_serializable_converter.dart index 102a724..686fd33 100644 --- a/lib/chopper/json_serializable_converter.dart +++ b/lib/chopper/json_serializable_converter.dart @@ -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 Function(Map json); @@ -27,7 +26,7 @@ class JsonSerializableConverter extends JsonConverter { Never _logAndThrow(String message) { final exception = JsonConverterException(T, message: message); - BoltLogger.zap(exception, tag: '$T', level: Level.WARNING); + BoltLogger.surge(exception, tag: '$T'); throw exception; } diff --git a/lib/logger/bolt_logger.dart b/lib/logger/bolt_logger.dart index addc193..f8be68c 100644 --- a/lib/logger/bolt_logger.dart +++ b/lib/logger/bolt_logger.dart @@ -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]. /// diff --git a/lib/logger/charges/debug_console_charge.dart b/lib/logger/charges/debug_console_charge.dart index 8d827f3..8233e1f 100644 --- a/lib/logger/charges/debug_console_charge.dart +++ b/lib/logger/charges/debug_console_charge.dart @@ -22,13 +22,22 @@ class DebugConsoleCharge implements BoltCharge { } List _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 @@ -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'; diff --git a/lib/logger/extensions/zap_extension.dart b/lib/logger/extensions/zap_extension.dart index db12c02..5b1100f 100644 --- a/lib/logger/extensions/zap_extension.dart +++ b/lib/logger/extensions/zap_extension.dart @@ -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 diff --git a/skills/dcc-create-blocful-page/SKILL.md b/skills/dcc-toolkit-create-blocful-page/SKILL.md similarity index 99% rename from skills/dcc-create-blocful-page/SKILL.md rename to skills/dcc-toolkit-create-blocful-page/SKILL.md index bbec17f..509514e 100644 --- a/skills/dcc-create-blocful-page/SKILL.md +++ b/skills/dcc-toolkit-create-blocful-page/SKILL.md @@ -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 diff --git a/skills/dcc-create-paginated-cubit/SKILL.md b/skills/dcc-toolkit-create-paginated-cubit/SKILL.md similarity index 99% rename from skills/dcc-create-paginated-cubit/SKILL.md rename to skills/dcc-toolkit-create-paginated-cubit/SKILL.md index 9828dff..d64bf07 100644 --- a/skills/dcc-create-paginated-cubit/SKILL.md +++ b/skills/dcc-toolkit-create-paginated-cubit/SKILL.md @@ -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 diff --git a/skills/dcc-setup-bolt-logger/SKILL.md b/skills/dcc-toolkit-setup-bolt-logger/SKILL.md similarity index 91% rename from skills/dcc-setup-bolt-logger/SKILL.md rename to skills/dcc-toolkit-setup-bolt-logger/SKILL.md index ca27531..775bc0e 100644 --- a/skills/dcc-setup-bolt-logger/SKILL.md +++ b/skills/dcc-toolkit-setup-bolt-logger/SKILL.md @@ -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 @@ -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 @@ -45,13 +45,13 @@ 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) | -| `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()` | @@ -59,6 +59,7 @@ import 'package:dcc_toolkit/common/run_app_bootstrap.dart'; ### 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 @@ -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 @@ -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'); ``` @@ -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' } @@ -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. diff --git a/skills/dcc-setup-kleurplaat-boekwerk/SKILL.md b/skills/dcc-toolkit-setup-kleurplaat-boekwerk/SKILL.md similarity index 99% rename from skills/dcc-setup-kleurplaat-boekwerk/SKILL.md rename to skills/dcc-toolkit-setup-kleurplaat-boekwerk/SKILL.md index 283a6d4..f51f96d 100644 --- a/skills/dcc-setup-kleurplaat-boekwerk/SKILL.md +++ b/skills/dcc-toolkit-setup-kleurplaat-boekwerk/SKILL.md @@ -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 diff --git a/test/logger/bolt_logger_test.dart b/test/logger/bolt_logger_test.dart index a1d281e..72efc3a 100644 --- a/test/logger/bolt_logger_test.dart +++ b/test/logger/bolt_logger_test.dart @@ -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) { @@ -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); }