diff --git a/CHANGELOG.md b/CHANGELOG.md
index 543461b..5a0f927 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### ✨ Features
- **Router Observers**: `MagicRouter.instance.addObserver()` enables NavigatorObserver integration for analytics/monitoring (Sentry, Firebase Analytics, custom observers). Observers are passed to GoRouter automatically. (#31)
- **Network Driver Plugin Hook**: `DioNetworkDriver.configureDriver()` exposes the underlying Dio instance for SDK integrations (sentry_dio, certificate pinning, custom adapters). (#32)
+- **Custom Log Drivers**: `LogManager.extend()` enables custom LoggerDriver registration (Sentry, file, Slack). Config-driven resolution with built-in override support. (#33)
## [1.0.0-alpha.6] - 2026-04-05
diff --git a/doc/digging-deeper/logging.md b/doc/digging-deeper/logging.md
index 72e3d08..1ab7463 100644
--- a/doc/digging-deeper/logging.md
+++ b/doc/digging-deeper/logging.md
@@ -219,33 +219,24 @@ class SentryLoggerDriver extends LoggerDriver {
### Registering the Custom Driver
-Register your driver in a Service Provider:
+Register your driver via `LogManager.extend()` in a Service Provider's `boot()` phase:
```dart
// lib/app/providers/logging_service_provider.dart
class LoggingServiceProvider extends ServiceProvider {
+ LoggingServiceProvider(super.app);
+
@override
- void register() {
- // Extend LogManager to support custom drivers
- app.extend('log', (manager) {
- final logManager = manager as LogManager;
-
- // Register custom driver factory
- logManager.registerDriver('sentry', (config) {
- return SentryLoggerDriver(
- dsn: config['dsn'] ?? '',
- minLevel: config['level'] ?? 'error',
- );
- });
-
- return logManager;
- });
+ Future boot() async {
+ LogManager.extend('sentry', (config) => SentryLoggerDriver(
+ dsn: config['dsn'] ?? '',
+ minLevel: config['level'] ?? 'error',
+ ));
}
}
```
-> [!NOTE]
-> The `registerDriver` method is a convention. If `LogManager` doesn't support it natively, you can extend it or customize resolution logic.
+This follows the same `extend()` pattern as `Auth.manager.extend(...)` for custom auth guards. The factory receives the channel's config map and returns a `LoggerDriver` instance.
Then use it in your config:
@@ -271,7 +262,17 @@ Log.channel('sentry').error('Something went wrong', {
## Example: Slack Notifications
-Here's a complete example for Slack webhook logging:
+Register the driver, then use it via config:
+
+```dart
+// In ServiceProvider boot()
+LogManager.extend('slack', (config) => SlackLoggerDriver(
+ webhookUrl: config['webhook_url'],
+ channel: config['channel'] ?? '#alerts',
+));
+```
+
+Here's the driver implementation:
```dart
class SlackLoggerDriver extends LoggerDriver {
diff --git a/lib/src/logging/log_manager.dart b/lib/src/logging/log_manager.dart
index 0ca6211..b10e935 100644
--- a/lib/src/logging/log_manager.dart
+++ b/lib/src/logging/log_manager.dart
@@ -9,6 +9,22 @@ import 'drivers/stack_logger_driver.dart';
class LogManager {
LoggerDriver? _cachedDriver;
+ static final Map)>
+ _customDrivers = {};
+
+ /// Register a custom log driver factory.
+ static void extend(
+ String driver,
+ LoggerDriver Function(Map config) factory,
+ ) {
+ _customDrivers[driver] = factory;
+ }
+
+ /// Reset all custom drivers (for testing).
+ static void resetDrivers() {
+ _customDrivers.clear();
+ }
+
/// Get the default logger driver based on configuration.
LoggerDriver driver([String? channel]) {
if (_cachedDriver != null && channel == null) {
@@ -32,6 +48,10 @@ class LogManager {
final channelConfig = channels[name] as Map? ?? {};
final driverName = channelConfig['driver'] ?? 'console';
+ if (_customDrivers.containsKey(driverName)) {
+ return _customDrivers[driverName]!(channelConfig);
+ }
+
switch (driverName) {
case 'stack':
return _createStackDriver(channelConfig);
diff --git a/skills/magic-framework/references/secondary-systems.md b/skills/magic-framework/references/secondary-systems.md
index 702b337..4dd4c6f 100644
--- a/skills/magic-framework/references/secondary-systems.md
+++ b/skills/magic-framework/references/secondary-systems.md
@@ -169,6 +169,19 @@ Log.channel('slack').error('Critical server issue');
- **`console`**: Outputs to standard output with configurable log level.
- **`stack`**: Aggregates multiple drivers (e.g., console + file simultaneously).
+### Custom Drivers
+
+Register custom log drivers via `LogManager.extend()` — follows the same pattern as `Auth.manager.extend(...)`:
+
+```dart
+// In a ServiceProvider boot():
+LogManager.extend('sentry', (config) => SentryLoggerDriver(
+ minLevel: config['level'] ?? 'warning',
+));
+```
+
+Custom drivers implement the `LoggerDriver` abstract class. They can be referenced in config by name and included in stack channels.
+
### Configuration
```dart
@@ -178,12 +191,16 @@ Log.channel('slack').error('Critical server issue');
'channels': {
'stack': {
'driver': 'stack',
- 'channels': ['console'],
+ 'channels': ['console', 'sentry'],
},
'console': {
'driver': 'console',
'level': 'debug',
},
+ 'sentry': {
+ 'driver': 'sentry',
+ 'level': 'warning',
+ },
},
}
```
diff --git a/test/logging/logging_test.dart b/test/logging/logging_test.dart
index 6148786..9a4fe72 100644
--- a/test/logging/logging_test.dart
+++ b/test/logging/logging_test.dart
@@ -13,6 +13,19 @@ class MockLoggerDriver extends LoggerDriver {
void clear() => logs.clear();
}
+/// A custom logger driver for testing extend() registration.
+class _CustomLoggerDriver extends LoggerDriver {
+ final List