Monolog handler that forwards Laravel logs to Inpsyde's Wonolog - the professional WordPress logging solution.
Works with any Laravel + WordPress setup: Acorn (w/wo Sage), WP Starter, Corcel, or custom integrations.
- Clean Laravel Syntax - Use
Log::error(),Log::info(), etc. anywhere in your code - Graceful Degradation - Works with or without Wonolog active
- wpify/scoper Support - Automatically detects scoped Wonolog namespace
- Zero Configuration - Works out of the box with sensible defaults
- Flexible Propagation - Control whether to stop at Wonolog or continue to other handlers
- PHP >= 8.2
- WordPress >= 6.0
- Laravel Illuminate/Support ^10.0|^11.0|^12.0
- Monolog ^2.0|^3.0
Note: Inpsyde's Wonolog is not required for the package to work. Without Wonolog, logs gracefully pass through to other handlers in your stack (e.g., file logging).
In your Laravel + WordPress project (Sage theme, WP Starter, etc.):
composer require wp-spaghetti/wonolog-handlerThe package auto-registers via service provider discovery.
For email notifications, sensitive data filtering, and advanced logging features, install the WP Spaghetti Wonolog mu-plugin, that provides a complete logging solution with production-ready configuration.
See the WP Spaghetti Wonolog documentation for setup and configuration options.
Update your config/logging.php:
<?php
use WpSpaghetti\WonologHandler\Handler\WonologHandler;
return [
'default' => env('LOG_CHANNEL', 'stack'),
'channels' => [
// Recommended: Stack with Wonolog + file backup
'stack' => [
'driver' => 'stack',
'channels' => ['wonolog', 'single'],
'ignore_exceptions' => false,
],
// Wonolog channel
'wonolog' => [
'driver' => 'monolog',
'handler' => WonologHandler::class,
'level' => env('LOG_LEVEL', 'debug'),
],
// File backup (optional but recommended)
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
],
];See examples/logging.php for a complete configuration example.
use Illuminate\Support\Facades\Log;
// Anywhere in your Laravel + WordPress code
Log::debug('Debugging information');
Log::info('Informational message');
Log::notice('Normal but significant event');
Log::warning('Warning condition');
Log::error('Error condition');
Log::critical('Critical condition');
Log::alert('Action must be taken immediately');
Log::emergency('System is unusable');Log::error('Payment failed', [
'user_id' => $userId,
'amount' => $amount,
'error' => $exception->getMessage(),
]);
// Wonolog channels - use UPPERCASE by convention
Log::error('Security breach', [
'channel' => 'SECURITY', // ✅ Correct
'ip' => $ipAddress,
]);
// Avoid lowercase - may not be tracked
// Log::error('Breach', ['channel' => 'security']); // ❌ May not work// Use only Wonolog (no file backup)
Log::channel('wonolog')->error('Critical error');
// Use only file logging
Log::channel('single')->debug('Debug info');
// Use stack (Wonolog + file) - recommended
Log::channel('stack')->warning('Warning message');// In app/Controllers/App.php or any controller
use Illuminate\Support\Facades\Log;
public function index()
{
Log::info('Page viewed', ['url' => request()->url()]);
return $this->view;
}// In your custom plugins or theme
use Illuminate\Support\Facades\Log;
add_action('init', function() {
Log::info('WordPress initialized');
});use Corcel\Model\Post;
use Illuminate\Support\Facades\Log;
$posts = Post::published()->get();
Log::info('Fetched posts', ['count' => $posts->count()]);See examples/usage.php for more real-world examples including WordPress hooks, WooCommerce integration, API logging, and performance monitoring.
To customize settings, publish the config:
# Sage/Acorn
wp acorn vendor:publish --tag=wonolog-handler-config
# WP Starter (using Laravel's artisan)
php vendor/bin/wp-starter vendor:publish --tag=wonolog-handler-configThis creates config/wonolog.php in your project:
<?php
return [
// Custom Wonolog namespace (for wpify/scoper)
'namespace' => env('WONOLOG_NAMESPACE', 'Inpsyde\\Wonolog'),
// Custom action hook (for wpify/scoper or custom naming)
'action' => env('WONOLOG_ACTION', 'wonolog.log'),
// Stop propagation when Wonolog is active?
'stop_propagation' => env('WONOLOG_STOP_PROPAGATION', false),
];If Wonolog is scoped, override the namespace:
Via config:
// config/wonolog.php
'namespace' => 'WpSpaghetti\\Deps\\Inpsyde\\Wonolog',Via filter:
add_filter('wonolog_handler.namespace', function () {
return 'WpSpaghetti\\Deps\\Inpsyde\\Wonolog';
});Via environment:
WONOLOG_NAMESPACE="WpSpaghetti\\Deps\\Inpsyde\\Wonolog"If Wonolog uses a custom action hook name:
Via config:
// config/wonolog.php
'action' => 'custom_wonolog.log',Via filter:
add_filter('wonolog_handler.action', function () {
return 'custom_wonolog.log';
});Via environment:
WONOLOG_ACTION="custom_wonolog.log"By default, logs continue to other handlers in the stack after Wonolog (allowing file backup). You can change this:
Stop at Wonolog (no file backup):
// config/wonolog.php
'stop_propagation' => true,Or via environment:
WONOLOG_STOP_PROPAGATION=trueUse cases:
false(default): Wonolog + file backup - recommended for productiontrue: Only Wonolog - if you don't want file logs and trust Wonolog completely
Laravel Log::error()
↓
Monolog LogRecord
↓
WonologHandler
↓ (if Wonolog active)
do_action('wonolog.log')
↓
Wonolog Processing
├─ Email notifications
├─ WordPress database
├─ Custom handlers
└─ Filtering/redaction
↓ (if stop_propagation=false)
Continue to next handler (file, Slack, etc.)
Channel Handling:
- Wonolog expects
channelat the top level of the action array, not insidecontext - If user provides
'channel' => 'SECURITY'in context, it's extracted and moved to top level - The channel is removed from context after extraction to avoid duplication
- If no channel is provided, it's NOT passed to Wonolog (Wonolog uses its default: DEBUG)
- Monolog's channel (e.g.,
stack,development) is NEVER used - it has nothing to do with Wonolog
Example behavior:
// User specifies Wonolog channel
Log::error('Error', ['channel' => 'SECURITY', 'ip' => '1.2.3.4']);
// Result: Channel = SECURITY, Context = ['ip' => '1.2.3.4'] (no 'channel' key)
// No channel specified
Log::error('Error');
// Result: Channel = DEBUG (Wonolog's default), Context = [] (empty except datetime/extra)
// Monolog channel is ignored
Log::channel('stack')->error('Error');
// Result: Channel = DEBUG (Wonolog's default), NOT 'stack'PSR-3 Placeholder Compatibility:
- The handler uses array format when calling
do_action('wonolog.log', [...]) - This forces Wonolog's
HookLogFactory::fromArray()method instead offromString() - Fixes PSR-3 placeholder substitution (e.g.,
{url},{handle}) which breaks infromString() - Compatible with all Wonolog v2.x and v3.x versions
Extra Data:
- Monolog's
extradata is passed as$context['extra'](following Wonolog's convention) - Datetime is passed as
$context['datetime']for full compatibility
Without Wonolog mu-plugin:
- WonologHandler detects Wonolog is not active
- Handler does nothing and returns
false - Logs continue to other handlers (files, etc.)
- No errors or warnings
With Wonolog mu-plugin:
- Handler forwards logs to Wonolog
- Wonolog processes with email, filtering, etc.
- Logs optionally continue to file backup (based on
stop_propagation)
The handler automatically detects Wonolog's namespace:
- Checks default
Inpsyde\Wonolog - Applies filter
wonolog_handler_namespace - Supports scoped namespaces from wpify/scoper
- Verifies
Configurator::ACTION_SETUPwas triggered - Caches result for performance
Check if Wonolog is active:
use WpSpaghetti\WonologHandler\Support\WonologDetector;
$detector = app(WonologDetector::class);
if (!$detector->isActive()) {
echo "Wonolog is not active!";
echo "Namespace: " . $detector->getNamespace();
echo "Action: " . $detector->getAction();
}Override via filter or config (see Advanced Configuration).
Understanding channel behavior:
-
Custom Wonolog channel (when explicitly provided):
Log::error('Security breach', ['channel' => 'SECURITY', 'ip' => '1.2.3.4']); // Email: Channel = SECURITY, Context = ['ip' => '1.2.3.4'] (no 'channel' key)
-
Default Wonolog channel (when not provided):
Log::error('Error'); // Email: Channel = DEBUG (Wonolog's default) Log::channel('stack')->error('Error'); // Email: Channel = DEBUG (Wonolog's default - Monolog channel is ignored) Log::channel('single')->error('Error'); // Email: Channel = DEBUG (Wonolog's default - Monolog channel is ignored)
-
Channel extraction:
- If
'channel'is in context, it's extracted and passed to Wonolog at top level - The
'channel'key is removed from context to avoid duplication - This ensures channel appears only once in emails (as "Channel: XXX", not in context)
- If
-
Monolog vs Wonolog channels:
- Monolog channels (
development,stack,single) route logs in Laravel - Wonolog channels (
DEBUG,SECURITY,HTTP) categorize logs in Wonolog - They are completely separate - Monolog channels are NOT sent to Wonolog
- To set a Wonolog channel:
Log::error('msg', ['channel' => 'SECURITY'])
- Monolog channels (
-
Channel naming conventions
⚠️ :- IMPORTANT: Wonolog uses UPPERCASE channel names by convention
- Standard Wonolog channels:
DEBUG,SECURITY,HTTP,DB,PHP-ERROR,CRON, etc. - Using lowercase (e.g.,
'security'instead of'SECURITY') may cause logs not to be tracked - Using non-configured channels (e.g.,
'FOO') may also not be tracked - This behavior depends on your Wonolog configuration and filters
- Best practice: Always use UPPERCASE for channel names
// ✅ Correct - uppercase Log::error('Breach', ['channel' => 'SECURITY']); // ❌ May not work - lowercase Log::error('Breach', ['channel' => 'security']); // ❌ May not work - non-configured channel Log::error('Error', ['channel' => 'FOO']);
-
Custom channel names:
- You can use custom channel names if configured in Wonolog
- Examples:
PAYMENT,API,WOOCOMMERCE, etc. - Make sure they're configured in your Wonolog setup
- Always use UPPERCASE for consistency
Ensure 'single' channel is in the stack:
'stack' => [
'driver' => 'stack',
'channels' => ['wonolog', 'single'], // ← Check this
],And ensure stop_propagation is false (default).
composer testSee LINKS file.
Please see CHANGELOG for a detailed list of changes for each release.
We follow Semantic Versioning and use Conventional Commits to automatically generate our changelog.
- Major versions (1.0.0 → 2.0.0): Breaking changes
- Minor versions (1.0.0 → 1.1.0): New features, backward compatible
- Patch versions (1.0.0 → 1.0.1): Bug fixes, backward compatible
All releases are automatically created when changes are pushed to the main branch, based on commit message conventions.
For your contributions please use:
See CONTRIBUTING for detailed guidelines.
