Why Bother with Proper Logging?
Let’s be honest – debugging can be a real pain. We’ve all been there, staring at a blank screen with no clue what went wrong. Or worse, getting vague error messages that lead nowhere. That’s where good logging comes in!
PHP’s built-in error reporting is… well, it’s there. But it’s pretty basic. It’s like trying to fix a complex machine with just a hammer. You need more specialized tools for serious development work.
That’s where Monolog enters the picture. Think of it as your debugging Swiss Army knife – it gives you a ton of options to see what’s really happening in your code.
Looking for a log navigator? Checkout my article about LNAV.
Looking for a external error logger or Sentry alternative? Checkout these … Bugsink / Glitchtip.
What’s Monolog Anyway?
Monolog is basically the rock star of PHP logging libraries. It’s what all the cool frameworks use (Laravel, Symfony – you name it). With millions of downloads, it’s become the go-to solution for anyone who needs to know what’s happening in their application.
The beauty of Monolog is how flexible it is. Want to log to files? Sure. Databases? No problem. Email, Slack, or pretty much anywhere else? Monolog has got you covered. It plays nicely with other systems too, thanks to following the PSR-3 standard (fancy talk for “it works well with other logging tools”).
Why Monolog Rocks for Debugging
Here’s why Monolog is your new best friend when it comes to squashing bugs:
- It’s multi-talented – Send your logs to different places at once (like keeping detailed logs in a file while getting urgent errors via email)
- It knows how serious things are – With eight different levels from “just FYI” to “everything’s on fire!”, you can filter what matters
- It’s context-aware – Automatically add extra info to your logs like user data or request details
- It’s pretty – Customize how your logs look so they’re actually readable
- It’s organized – Group logs by channels so you’re not searching through a haystack
All this means you spend less time scratching your head and more time actually fixing problems!
Getting Started with Monolog
Installation: Quick and Painless
First things first, let’s get Monolog installed. It’s as simple as:
1 2 3 |
composer require monolog/monolog |
That’s it! One line and you’re ready to roll. (If you’re new to Composer, check out the getting started guide first.)
Basic Setup: Your First Debug Logger
Here’s a simple setup to get you started:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Get Composer's autoloader in the mix require __DIR__ . '/vendor/autoload.php'; // Bring in what we need from Monolog use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Processor\IntrospectionProcessor; // Create our debug superhero $logger = new Logger('debug'); // Tell it where to save the day (I mean, logs) $logger--->pushHandler(new StreamHandler(__DIR__.'/logs/debug.log', Logger::DEBUG)); // Give it some extra powers (like knowing where the log came from) $logger->pushProcessor(new IntrospectionProcessor()); // Now let's use it! $logger->debug('Checking if user #123 is signed in', ['username' => 'bug_squasher']); |
This creates a logger that will capture all your debug info in a file and tell you exactly which line of code triggered each log. Pretty neat, right?
Making the Most of Monolog
Log Levels: Choose Your Battle
Monolog gives you eight different log levels, from “I’m just being chatty” to “EVERYTHING IS BROKEN!”:
- DEBUG: “Hey, just FYI, this happened” (for development)
- INFO: “Something normal but noteworthy happened”
- NOTICE: “This is unusual but not worrying”
- WARNING: “This isn’t great but we’re still functioning”
- ERROR: “Houston, we have a problem”
- CRITICAL: “Major component down!”
- ALERT: “Someone needs to fix this NOW”
- EMERGENCY: “The building is on fire and everyone’s panicking”
When you’re debugging, you’ll mostly use the first three levels, saving the scarier ones for actual problems. Here’s how you might use them:
1 2 3 4 5 6 7 8 9 10 |
// When you're just being nosy about what's happening $logger->debug('Here's what that variable contains', ['the_var' => $mysterious_variable]); // When tracking normal application flow $logger->info('User tried to log in', ['username' => $username]); // When something's fishy but not broken $logger->warning('That image upload failed, using the default instead', ['error' => $error]); |
Context: The Secret Sauce
Want to make your logs actually useful? Add context! Compare these two:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Boring and unhelpful $logger->debug('Payment processed'); // Detailed and actually useful! $logger->debug('Payment processed', [ 'amount' => $amount, 'currency' => 'USD', 'customer_id' => $customer_id, 'payment_method' => $method, 'successful' => $success ]); |
See the difference? The second one tells you what’s actually going on. When you come back to these logs later (and trust me, you will), you’ll thank yourself for the extra details.
Cool Monolog Tricks for Debugging
Rotating Log Files (So Your Disk Doesn’t Explode)
Nobody wants a 10GB log file. Here’s how to create daily logs that clean up after themselves:
1 2 3 4 5 6 7 8 9 10 |
use Monolog\Handler\RotatingFileHandler; // Logs rotate daily and we keep a week's worth $logger->pushHandler(new RotatingFileHandler( __DIR__.'/logs/debug.log', 7, // days to keep Logger::DEBUG )); |
Browser Console Magic
When working on frontend stuff, why not send debug info right to your browser’s console?
1 2 3 4 5 6 7 8 |
use Monolog\Handler\BrowserConsoleHandler; // Only in development, obviously! if (APP_ENV === 'dev') { $logger->pushHandler(new BrowserConsoleHandler(Logger::DEBUG)); } |
Wake-Me-Up Alerts
For those “someone fix this now!” moments:
1 2 3 4 5 6 7 8 9 10 |
use Monolog\Handler\NativeMailerHandler; $logger->pushHandler(new NativeMailerHandler( 'you@example.com', '🔥 SITE ON FIRE 🔥', 'system@example.com', Logger::CRITICAL )); |
The Multi-Logger Approach
Why settle for one logging method when you can have several? Here’s a setup that gives you the best of all worlds:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// All the nitty-gritty details go here $logger->pushHandler(new StreamHandler(__DIR__.'/logs/debug.log', Logger::DEBUG)); // Just the "hmm, that's interesting" stuff and above $logger->pushHandler(new StreamHandler(__DIR__.'/logs/notice.log', Logger::NOTICE)); // Email me if something's actually broken $logger->pushHandler(new NativeMailerHandler( 'dev@example.com', 'Site Error Alert', 'alerts@example.com', Logger::ERROR )); |
Automatic Context: Be Lazy (In a Good Way)
Why manually add the same info to every log when you can automate it?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
use Monolog\Processor\WebProcessor; use Monolog\Processor\IntrospectionProcessor; use Monolog\Processor\MemoryUsageProcessor; // Add info about the web request $logger->pushProcessor(new WebProcessor()); // Add file:line where the log happened $logger->pushProcessor(new IntrospectionProcessor()); // See if your code is memory-hungry $logger->pushProcessor(new MemoryUsageProcessor()); |
Now every log entry automatically includes this info. It’s like having an assistant who fills in all the details you’d otherwise forget! Check out the full list of available processors.
Wonolog: Monolog for WordPress Fans
If you’re a WordPress developer, you might be thinking “This Monolog thing sounds great, but I work in WordPress-land!” Well, you’re in luck – that’s exactly where Wonolog comes in.
What’s Wonolog All About?
Wonolog is basically Monolog wearing a WordPress costume. It’s a special package that connects WordPress’s way of doing things with Monolog’s powerful logging features. The team at Inpsyde created it to let WordPress developers join the logging party.
The cool part? You don’t have to rewrite your WordPress code to use it. Wonolog listens for WordPress actions and hooks, then does the logging for you.
Getting Wonolog Up and Running
Quick Install
Just like Monolog, installation is a one-liner:
1 2 3 |
composer require inpsyde/wonolog |
Super Simple Setup
Wonolog’s setup is ridiculously easy:
- Make sure Composer’s autoloader is loaded early in WordPress
- Create a super-simple must-use plugin with this tiny code:
1 2 3 4 |
// That's it. Seriously. Inpsyde\Wonolog\bootstrap(); |
Tada! You now have powerful logging for WordPress. Wonolog will create daily log files in {WP_CONTENT_DIR}/wonolog/{Y/m/d}.log
– for example, /wp-content/wonolog/2023/04/24.log
.
What Gets Logged Automatically
Wonolog is smart about what it logs based on your WP_DEBUG_LOG
setting:
- If
WP_DEBUG_LOG
istrue
: “Log all the things!” - If
WP_DEBUG_LOG
isfalse
: “Only log the scary stuff” (ERROR level and above)
Right out of the box, Wonolog catches:
- PHP notices, warnings, and errors (oops!)
- Exceptions that nobody caught (double oops!)
- WordPress errors like database problems, HTTP API fails, wp_mail() issues, and those pesky 404s
WordPress-Style Debugging with Wonolog
The WordPress Way to Log
The real magic of Wonolog is how it works with WordPress’s action system. Want to log something? Just do:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Simple debug log do_action('wonolog.debug', 'Testing if shortcodes are working'); // With all the juicy details do_action( 'wonolog.debug', 'User updated their profile', [ 'user_id' => $user->ID, 'fields_changed' => $changed_fields, 'came_from' => wp_get_referer() ] ); |
See how natural that feels in WordPress? No need to create logger objects or remember Monolog syntax – just use WordPress actions like you normally would.
Keeping Things Organized with Channels
If you’re working on a bigger project, you’ll want to organize your logs. Wonolog lets you create custom channels:
1 2 3 4 5 6 7 8 9 10 11 12 |
// In your e-commerce plugin do_action( 'wonolog.log', [ 'level' => 'debug', 'message' => 'New order being processed', 'context' => ['order_id' => $order_id, 'total' => $total], 'channel' => 'my_shop_orders' ] ); |
Now all your shop order logs are grouped together, making it way easier to track down issues.
WordPress-Specific Debugging
Here are some cool tricks for debugging WordPress components:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// See what database queries are running add_action('query', function($query) { do_action( 'wonolog.debug', 'SQL query executed', [ 'query' => $query, 'backtrace' => wp_debug_backtrace_summary() ], 'database' ); }); // Track REST API activity add_action('rest_api_init', function() { add_filter('rest_pre_dispatch', function($result, $server, $request) { do_action( 'wonolog.debug', 'REST API request received', [ 'endpoint' => $request->get_route(), 'method' => $request->get_method(), 'params' => $request->get_params() ], 'rest_api' ); return $result; }, 10, 3); }); |
Taking Wonolog to the Next Level
Custom Log Files
Want your logs somewhere specific? No problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
add_filter('wonolog.handler-factory', function($factory) { // Create a custom handler for your plugin $factory->addCustomFactory('my_plugin_handler', function(array $config) { // Log to a specific file return new StreamHandler( WP_CONTENT_DIR . '/my-plugin/debug.log', Logger::DEBUG ); }); return $factory; }); // Use your custom handler add_filter('wonolog.default-hook-listeners', function($listeners) { $listeners['debug']->useHandlers(['my_plugin_handler']); return $listeners; }); |
Different Logging for Different Environments
You don’t want the same logging setup in development and production. Here’s how to adjust based on environment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
add_filter('wonolog.bootstrap-config', function($config) { // Check which environment we're in if (defined('WP_ENV') && WP_ENV === 'development') { // For development: log ALL the things! $config['default_handlers']['minimum_level'] = Logger::DEBUG; $config['use_buffer'] = false; // Log immediately } elseif (defined('WP_ENV') && WP_ENV === 'staging') { // For staging: be a bit more selective $config['default_handlers']['minimum_level'] = Logger::INFO; } else { // For production: just the important stuff $config['default_handlers']['minimum_level'] = Logger::WARNING; } return $config; }); |
Auto-Magical WordPress Info
Want to add WordPress-specific details to all your logs automatically? Here’s how:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
add_filter('wonolog.processor-factory', function($factory) { // Create a processor that adds WP info $factory->addCustomFactory('wp_debug_info', function() { return function(array $record) { // Add useful WordPress details $record['extra']['wp_version'] = get_bloginfo('version'); $record['extra']['theme'] = wp_get_theme()->get('Name'); $record['extra']['active_plugins'] = count(get_option('active_plugins')); $record['extra']['memory_used'] = size_format(memory_get_usage()); return $record; }; }); return $factory; }); // Use your custom processor add_filter('wonolog.processor-record', function($processors) { $processors[] = 'wp_debug_info'; return $processors; }); |
Pro Tips for Debugging with Wonolog
- Dev vs Prod: Log everything in development, be picky in production
- Organize with Channels: Create separate channels for different parts of your site
- Context is King: Always include user IDs, post IDs, and other relevant data
- Be Specific with Levels: Don’t mark everything as ERROR – be honest about severity
- Log All the Things (in Development): When building features, log liberally to understand what’s happening
- Keep Production Logs Clean: Nobody wants to sift through gigabytes of debug logs
- Think About Performance: In high-traffic sites, use buffered logging to reduce disk writes
Wrapping Up
Monolog and Wonolog might sound like fancy technical tools, but they’re really just about making your life easier as a developer. Think of them as your debugging buddies – there to help you figure out what’s going wrong and why.
With Monolog, you get powerful, flexible logging for any PHP application. With Wonolog, you get all that power in a WordPress-friendly package that just feels natural to use.
Start adding proper logging to your projects, and you’ll wonder how you ever lived without it. Trust me – your future self will thank you when you’re trying to track down that weird bug at 2 AM!
Want to learn more? Check out these resources:
Now go forth and log all the things (but maybe not in production)!
Bonus: Integrating with Sentry / Glitchtip / Bugsink
Sentry SDK must be present.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
use Monolog\Level; use Monolog\Logger; use Monolog\Handler\StreamHandler; \Sentry\init([ 'dsn' => 'YOUR DSN' ]); $log = new Logger('sentry'); $log->pushHandler(new \Sentry\Monolog\Handler( hub: \Sentry\SentrySdk::getCurrentHub(), level: Logger::ERROR, // Take note of the level here, messages with that level or higher will be sent to Sentry bubble: true, fillExtraContext: true, // Will add a `monolog.context` & `monolog.extra`, key to the event with the Monolog `context` & `extra` values )); |
FAQ
What is Monolog and why should I use it for debugging?
Monolog is the de-facto standard logging library for PHP that allows you to send your logs to files, sockets, inboxes, databases, and various web services. It’s powerful for debugging because it offers eight severity levels, multiple handlers, processors for adding context automatically, and formatters for customizing log appearance. Using Monolog gives you better visibility into your application’s behavior, making it easier to identify and fix issues.
How do I install Monolog in my PHP project?
Installing Monolog is simple using Composer. Just run:
1 2 3 |
composer require monolog/monolog |
This will add Monolog to your project and update your composer.json and composer.lock files.
What are the different log levels in Monolog and when should I use each?
Monolog follows the PSR-3 standard with eight log levels from lowest to highest severity:
- DEBUG: Detailed information for development
- INFO: Noteworthy events (user logins, SQL logs)
- NOTICE: Normal but significant events
- WARNING: Exceptional occurrences that aren’t errors
- ERROR: Runtime errors that don’t require immediate action
- CRITICAL: Critical conditions like component unavailability
- ALERT: Issues requiring immediate action
- EMERGENCY: System is unusable
For debugging, DEBUG, INFO, and WARNING are most commonly used, while higher levels are for actual error conditions.
How can I prevent my log files from getting too large?
You can use Monolog’s RotatingFileHandler to automatically rotate log files and limit their size:
1 2 3 4 5 6 7 8 9 |
use Monolog\Handler\RotatingFileHandler; $logger->pushHandler(new RotatingFileHandler( __DIR__.'/logs/app.log', 7, // Keep 7 days of logs Logger::DEBUG )); |
This creates daily log files and automatically deletes files older than the specified number of days.
How do I add contextual information to my logs?
You can add context by passing an array as the second parameter to any log method:
1 2 3 4 5 6 7 8 9 10 |
$logger->debug( 'User registration process started', [ 'email' => $email, 'referrer' => $referrer, 'timestamp' => time() ] ); |
You can also use processors to automatically add context to all logs, such as web request information, file/line where the log was triggered, and memory usage.
Can I send logs to multiple destinations at once?
Yes, Monolog allows you to use multiple handlers simultaneously. For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Log everything to a file $logger->pushHandler(new StreamHandler(__DIR__.'/logs/all.log', Logger::DEBUG)); // Only log errors to email $logger->pushHandler(new NativeMailerHandler( 'admin@example.com', 'Error Report', 'system@example.com', Logger::ERROR )); |
This setup logs all messages to a file, but only sends errors and above via email.
What is Wonolog and how does it relate to Monolog?
Wonolog is a WordPress-specific implementation of Monolog, created by Inpsyde. It bridges WordPress and Monolog, allowing WordPress developers to use Monolog’s powerful logging capabilities within WordPress’s ecosystem. Wonolog integrates with WordPress’s actions and hooks system, so you can log using familiar WordPress patterns without having to directly couple your code with Monolog.
How do I install Wonolog in WordPress?
Install Wonolog via Composer:
1 2 3 |
composer require inpsyde/wonolog |
Then create a must-use plugin with this minimal code:
1 2 3 4 5 |
<?php // Bootstrap Wonolog with default settings Inpsyde\Wonolog\bootstrap(); |
This simple setup will automatically start logging WordPress events to daily log files in the wp-content/wonolog/ directory.
What does Wonolog log by default?
By default, Wonolog logs the following:
- PHP notices, warnings, and errors
- Uncaught exceptions
- WordPress errors (database errors, HTTP API errors, wp_mail() errors, 404 errors)
When WP_DEBUG_LOG is true, Wonolog logs everything. When it’s false, Wonolog only logs events with a level of ERROR or higher.
How do I log my own custom events with Wonolog?
You can log custom events using WordPress actions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Basic logging do_action('wonolog.debug', 'Testing function X'); // With context do_action( 'wonolog.info', 'User updated profile', [ 'user_id' => $user_id, 'fields' => $updated_fields ] ); |
You can also create custom log channels:
1 2 3 4 5 6 7 8 9 10 11 |
do_action( 'wonolog.log', [ 'level' => 'debug', 'message' => 'Payment processed', 'context' => ['amount' => $amount], 'channel' => 'my_plugin_payments' ] ); |
How can I configure Wonolog to send error notifications?
You can customize Wonolog handlers to send notifications via email, Slack, or other services:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
add_filter('wonolog.handler-factory', function($factory) { // Add email handler for errors $factory->addCustomFactory('error_email', function(array $config) { return new NativeMailerHandler( 'admin@example.com', 'Site Error Alert', 'alerts@example.com', Logger::ERROR ); }); return $factory; }); // Use the custom handler add_filter('wonolog.default-hook-listeners', function($listeners) { $listeners['error']->useHandlers(['error_email']); return $listeners; }); |
How can I change where Wonolog stores log files?
You can change the log directory using the ‘wonolog.bootstrap-config’ filter:
1 2 3 4 5 6 7 |
add_filter('wonolog.bootstrap-config', function($config) { // Change log directory $config['log_dir'] = WP_CONTENT_DIR . '/my-custom-logs'; return $config; }); |
How do I use Monolog to debug browser/JavaScript issues?
For browser/JavaScript debugging, you can use Monolog’s BrowserConsoleHandler:
1 2 3 4 5 6 7 8 |
use Monolog\Handler\BrowserConsoleHandler; // Only add in development if (defined('WP_DEBUG') && WP_DEBUG) { $logger->pushHandler(new BrowserConsoleHandler(Logger::DEBUG)); } |
This handler sends log messages to the browser’s JavaScript console, which can be useful for debugging AJAX calls or other browser interactions.
Is there a way to format logs differently for different handlers?
Yes, you can use different formatters for different handlers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
use Monolog\Formatter\LineFormatter; use Monolog\Formatter\HtmlFormatter; use Monolog\Formatter\JsonFormatter; // For log file - simple line format $fileHandler = new StreamHandler('logs/app.log'); $fileHandler->setFormatter(new LineFormatter()); // For email - HTML format $emailHandler = new NativeMailerHandler('admin@example.com', 'Error Alert', 'system@example.com'); $emailHandler->setFormatter(new HtmlFormatter()); // For API/database storage - JSON format $apiHandler = new StreamHandler('php://stdout'); $apiHandler->setFormatter(new JsonFormatter()); |
This allows you to customize how logs appear in different destinations.
How do I setup different logging for development vs production in WordPress?
With Wonolog, you can configure environment-specific logging using the WP_ENV constant:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
add_filter('wonolog.bootstrap-config', function($config) { if (defined('WP_ENV')) { switch (WP_ENV) { case 'development': // Log everything immediately $config['default_handlers']['minimum_level'] = Logger::DEBUG; $config['use_buffer'] = false; break; case 'staging': // Medium verbosity $config['default_handlers']['minimum_level'] = Logger::INFO; break; case 'production': // Only important stuff $config['default_handlers']['minimum_level'] = Logger::WARNING; $config['use_buffer'] = true; // Buffer logs for better performance break; } } return $config; }); |
How do I log database queries in WordPress for debugging?
You can log WordPress database queries using Wonolog by hooking into the ‘query’ action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
add_action('query', function($query) { // Only log non-trivial queries (ignore common WordPress core queries) if (strpos($query, 'wp_') !== 0 && strlen($query) > 50) { do_action( 'wonolog.debug', 'Database query executed', [ 'query' => $query, 'backtrace' => wp_debug_backtrace_summary() ], 'database' ); } }); |
This logs database queries along with a backtrace to help you find where queries are being generated.