The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Monolog Logger Version Upgrades: A Guide to Smooth Transitions


In his book The Cathedral & the Bazaar, Eric S. Raymond observes that “perfect software is achieved not when there is nothing more to add, but when there is nothing more to take away.” While Raymond was describing the evolutionary nature of open source development, his insight applies equally well to libraries like Monolog. Each major version represents both choices about what to keep and what to remove—trade-offs that affect how we, as developers, approach upgrades.

Monolog has served as PHP’s de facto standard logging library for over a decade. Its stability and comprehensive feature set have made it a trusted component in countless applications. Though version 2 has proven remarkably durable, Monolog 3 brings significant changes that reflect PHP’s evolution toward stricter type safety and modern practices. For many of us, upgrading is not a question of if but when.

In this guide, we’ll walk through upgrading from Monolog 2 to Monolog 3 methodically. We’ll examine what changed, explore why those changes were necessary, and develop a practical approach you can follow safely in your own projects. By the end, you’ll understand both the technical steps and the reasoning behind them—so you can make informed decisions about when and how to upgrade.

Why Upgrade Monolog?

Before we discuss how to upgrade, let’s consider why you might choose to do so. Upgrading a dependency is not a decision to take lightly—it involves testing, potential code changes, and the risk of introducing new bugs. However, there are compelling reasons to keep Monolog current:

  • Security maintenance. Like any actively maintained library, Monolog occasionally releases patches for security vulnerabilities. Staying on a supported version ensures you receive these updates. Of course, older versions may remain secure for years, but eventually you’ll face a security issue that only newer releases address.

  • Performance improvements. The Monolog maintainers regularly identify and implement optimizations. These can range from micro-optimizations in hot paths to architectural changes that reduce memory usage. In high-traffic applications, even small efficiency gains can translate into meaningful resource savings.

  • New capabilities. Each release introduces new handlers, formatters, and processors that extend Monolog’s reach. If you need to integrate with a newer service or require a specific formatting option, an upgrade may be the most straightforward path.

  • Bug fixes and stability. Production logging is not an area where you want surprises. Every release includes fixes for reported issues, improving reliability across a wide range of use cases.

That said, upgrading is not without cost. You will need to allocate time for testing, and you may encounter breaking changes that require code modifications. The key is to approach upgrades methodically rather than treating them as chores to be rushed.

Understanding the Upgrade Commitment

Before we dive into the technical steps, let’s acknowledge the scope of what we’re undertaking. Upgrading from Monolog 2 to 3 is not inherently difficult, but it does require diligence. Though the core changes are well-documented, each application has its own usage patterns—custom handlers, third-party integrations, edge cases—that can surface unexpected issues.

Of course, you might be wondering: “Can’t we just update the composer.json and be done?” In simple projects with minimal customizations, that approach sometimes works. Though for anything beyond a trivial setup, you’ll need to verify behavioral compatibility thoroughly. The time you invest upfront in preparation pays dividends in avoiding production incidents later.

With that said, let’s examine how to prepare systematically.

Warnings and Caveats

Upgrading a logging library might seem like a low-risk change—after all, logs are often considered secondary to application functionality. That said, it’s important to acknowledge that logging can be critical for debugging, auditing, and compliance. A broken logging system can leave you in the dark when you need visibility most.

Here are key risks to consider before proceeding:

  • PHP 8.1 requirement. Monolog 3 requires PHP 8.1 or higher. If your production environment runs an older PHP version, you’ll need to upgrade PHP first. This can be a significant undertaking in itself, especially if you’re on an older distribution with LTS constraints. Before committing to Monolog 3, verify that your entire stack supports PHP 8.1—including your CI/CD environment, staging servers, and any third-party services that execute your code (like queue workers). Of course, if you’re already on PHP 8.2 or 8.3, this is not a concern.

  • Third-party package compatibility. Your application may depend on packages that themselves depend on Monolog. Packages like symfony/monolog-bundle, laravel/framework, or other logging integrations may need to be compatible with Monolog 3. Check your composer why monolog/monolog output to see the dependency tree. Some packages may not yet support Monolog 3 if they’re unmaintained. In such cases, you have three options: upgrade the package to a compatible version, replace it with an alternative, or maintain Monolog 2 on your system while the package catches up. Though many popular packages have already added support for Monolog 3, it’s worth confirming before you upgrade.

  • Custom handlers, formatters, and processors. If you’ve written custom Monolog extensions, you’ll need to update them for Monolog 3. The changes to LogRecord type and level constants are the main culprits. Even if your custom code uses only the public API, subtle changes in method signatures or return types can cause errors. One may wonder: “Will my custom handler still work?” The answer depends on how closely it interacts with Monolog’s internals. Typically, handlers that only implement the HandlerInterface and process LogRecord objects with object property access will need minor updates at most. Those that directly manipulate log record arrays or rely on deprecated constants will need more significant changes.

  • Testing gaps. If your application lacks tests that exercise logging, you may miss regressions until they appear in production. Consider adding at least a few simple tests that verify: log messages reach their destinations, context data is captured correctly, and custom processors/handlers behave as expected. Of course, comprehensive testing is ideal, but even basic smoke tests provide a safety net.

  • Performance considerations. Though Monolog 3 is generally optimized for PHP 8.1’s performance improvements, the switch from arrays to objects for log records may have memory and speed implications. In high-throughput applications (logging thousands of messages per second), you may notice differences—either positive or negative. We recommend benchmarking your logging throughput before and after the upgrade in a staging environment. Keep an eye on memory usage as well; objects can increase memory consumption slightly compared to arrays, though the difference is typically negligible relative to overall application memory.

  • Deprecation notices. If you run Monolog 3 with code that still uses the old integer constants (e.g., Logger::WARNING), those constants remain defined but trigger deprecation notices. In development, these notices can clutter your logs and potentially cause test failures if your test suite treats notices as errors. Though you can suppress deprecation notices temporarily, the long-term solution is to update your code to use the Level enum. Be prepared to address deprecation warnings during the transition.

  • Rollback readiness. Before upgrading, ensure you can revert quickly if needed. This means: commit your composer.json and composer.lock before updating, tag the release, and possibly have a hot-rollback plan. Though we anticipate a smooth upgrade with proper preparation, it’s prudent to have an escape route. One must assume that unforeseen compatibility issues could emerge under production load that didn’t surface in testing.

  • Log rotation and external systems. If your log handlers write to files that are rotated by external tools (logrotate, etc.), verify that file permissions and paths remain compatible after the upgrade. Though Monolog itself doesn’t typically change these, configuration drift can occur if you accidentally modify handler settings during the upgrade. Double-check that file handlers still point to the intended locations and that rotation policies remain intact.

By acknowledging these risks upfront, we can approach the upgrade with eyes open—not to instill fear, but to encourage methodical preparation. The next sections will guide you through each step, building on this foundation of awareness.

Assessing Your Current Usage

First, we need to understand what we’re working with. You can analyze your project’s Monolog usage in several ways:

Check your composer.json to see the current version constraint:

$ grep monolog composer.json
        "monolog/monolog": "^2.0",

Of course, this only tells us the declared dependency, not how extensively Monolog is used throughout your codebase.

Search your codebase for Monolog imports and usage:

$ grep -r "Monolog\\\\" app/ src/ | wc -l

Or more specifically, look for custom handlers, formatters, and processors:

$ find . -name "*.php" -exec grep -l "extends.*Handler\|implements.*Processor" {} \;

Though these commands give us a count, they don’t tell us the complexity of those custom implementations. We’ll need to review any custom classes that interact with Monolog’s internals.

If you have a large codebase, you might also consider using static analysis tools like PHPStan or Psalm to identify type-related issues that could surface during the upgrade. Though not strictly required, these tools can highlight potential problems before they manifest at runtime.

Alternative Upgrade Approaches

There is no one-size-fits-all approach to upgrading Monolog. Depending on your project’s constraints, you might consider different strategies:

Option 1: Direct Big-Bang Upgrade

This approach involves updating Monolog directly from version 2 to 3 across your entire application and fixing any issues that arise. It’s the simplest approach in terms of process—just update composer.json and run composer update—but it can be more challenging to debug if widespread issues emerge.

This approach works well when:

  • Your application has good test coverage (unit, integration, and preferably some end-to-end tests)
  • Your Monolog usage is relatively straightforward
  • You have a staging environment that closely mirrors production

Option 2: Incremental Upgrade with Compatibility Layer

Though Monolog 3 has dropped backward compatibility, you can sometimes bridge the gap by introducing compatibility code that adapts to both versions. For example, you could write a wrapper around log record access that checks whether records are arrays or objects:

function getLogLevel(array|LogRecord $record): mixed {
    return is_array($record) ? $record['level'] : $record->level;
}

This approach allows you to upgrade Monolog gradually while updating code piecemeal. It’s more work initially but reduces risk if you need to keep the application running continuously.

Option 3: Parallel Logging (Dual-Write)

For mission-critical systems where downtime is unacceptable, you could run both Monolog 2 and Monolog 3 simultaneously, configuring loggers to write to separate targets. This isn’t a true upgrade—it’s a shadow deployment where you validate Monolog 3 behavior without disrupting existing logging. Once satisfied, you switch over completely.

This approach is complex and typically used only in high-availability environments.

Which approach makes sense for you? That depends on your testing capabilities, deployment flexibility, and risk tolerance. For most applications, Option 1 is fine provided you have adequate tests and a staging environment. Option 2 becomes valuable when your Monolog integration is extensive but you can’t dedicate a full development cycle to the upgrade. Option 3 is overkill for most projects but worth mentioning for completeness.

Essential Preparation Steps

Regardless of which strategy you choose, certain preparatory steps are non-negotiable:

1. Establish a baseline with passing tests.

If you don’t already have tests that exercise your logging code, now is the time to add at least a few basic checks. You don’t need 100% coverage—though that’s a worthy goal—but you should have tests that verify:

  • Log messages are written as expected to their targets
  • Custom handlers and processors behave correctly
  • Context data appears where it should

A simple test might look like:

public function test_logger_writes_to_file(): void
{
    $logger = new Logger('test');
    $handler = new StreamHandler('php://temp', Logger::DEBUG);
    $logger->pushHandler($handler);
    
    $logger->info('Test message', ['user_id' => 123]);
    
    $handler->close();
    // Verify output contains expected content
}

Of course, the exact structure depends on your testing framework (PHPUnit, Pest, etc.). The key is to have something that will catch regressions.

2. Document your current Monolog usage.

Create a quick inventory of:

  • Which handlers you’re using (StreamHandler, SyslogHandler, etc.)
  • Any third-party handlers from community packages
  • Custom processors and their purpose
  • How log levels are configured in different environments (development, staging, production)

This documentation doesn’t need to be elaborate—a simple markdown file or even a comment in your code suffices. The act of documenting forces you to understand what you have, and the resulting reference helps during the upgrade.

3. Freeze non-essential changes.

Before upgrading, commit all your current work and avoid making unrelated code changes during the upgrade process. This creates a clean baseline and makes it easier to isolate Monolog-related issues if they arise.

4. Prepare a rollback plan.

What happens if the upgrade reveals unresolvable issues? You should be able to revert to Monolog 2 quickly. That means:

  • Your composer.json should be in version control (obviously)
  • You should have a recent production backup or snapshot
  • Consider using a feature flag to toggle between old and new logging setups if you’re doing Option 2 or 3

Though rollback is rarely needed with good preparation, having the plan reduces stress and improves decision-making if problems arise.

Using a Staging Environment

We mentioned staging already, but let’s be more specific about what makes a good staging environment:

  • Same PHP version as production (or at least compatible with your target Monolog version)
  • Same operating system or as close as feasible (Linux distributions vary, but aim for the same family)
  • Same configuration for Monolog handlers (file paths, syslog destinations, etc.)
  • Synthetic traffic that mimics production patterns if you can’t use a copy of production data

If you don’t have a staging environment, you can simulate one on a local machine or a dedicated testing server. The goal is to catch environment-specific issues—like file permission problems or syslog configuration—before they hit production.

Testing with production-like data is valuable because logging often includes context that depends on your application’s domain. If your logs include user IDs, order numbers, or other business data, seeing how those flow through the system helps you verify that context handling hasn’t changed in unexpected ways.

Key Changes in Monolog 3

The evolution of Monolog provides important context for these changes. Originally released in 2010, Monolog grew organically over a decade, accumulating features while maintaining backwards compatibility with PHP 5.3. When Monolog 2 arrived in 2017, it added type hints but still supported older PHP versions. Monolog 3, first stable in 2023, takes full advantage of PHP 8.1’s modern features—native enums, readonly properties, and stricter typing. The core team made a deliberate choice: require PHP 8.1 to simplify the codebase and provide better static analysis, IDE support, and runtime safety. Though this breaks compatibility with older PHP versions, it aligns Monolog with current PHP best practices and ensures the library can evolve without legacy constraints.

Monolog 3 introduced a number of significant changes, including some backward compatibility breaks. Here are some of the most important changes you need to be aware of:

PHP Version Requirement

Monolog 3 requires PHP 8.1 or higher. If you are on an older version of PHP, you will need to upgrade PHP before you can upgrade Monolog.

Log Record and Level Changes

Monolog 3 introduces significant type-related changes that affect how log records are represented and how log levels are specified. If you have custom handlers, formatters, or processors, you’ll need to update your code.

Log records are now objects. In Monolog 2, log records were associative arrays. Your custom processors and handlers likely accessed fields like $record['context'] or $record['level']. In Monolog 3, records are instances of Monolog\LogRecord with public properties. You can still access them using array syntax because LogRecord implements ArrayAccess for backward compatibility, but the recommended approach is to use object properties:

Monolog 2 (array access):

$level = $record['level'];
$context = $record['context'];

Monolog 3 (object properties):

$level = $record->level;
$context = $record->context;

If you need an array for any reason, call $record->toArray().

Log levels are now enums. The integer constants like Logger::DEBUG, Logger::WARNING, etc., are deprecated. Instead, use the Monolog\Level enum:

Monolog 2:

use Monolog\Logger;

$logger->warning('This is a warning');
$handler = new StreamHandler('path/to/your.log', Logger::WARNING);

Monolog 3:

use Monolog\Logger;
use Monolog\Level;

$logger->warning('This is a warning'); // method names unchanged
$handler = new StreamHandler('path/to/your.log', Level::Warning);

Note that the convenience methods like $logger->warning() remain unchanged—only the level constants used as arguments have changed.

If you support both Monolog 2 and 3, you can often avoid changes by relying on the deprecated constants (they still work, but generate deprecation notices). For a clean, future-proof codebase, migrate to the Level enum.

Also, be aware that the Logger::API constant has been added, which can help you detect the Monolog API version at runtime if needed.

pushProcessor and popProcessor

The pushProcessor and popProcessor methods now return the logger instance ($this) instead of the processor that was pushed or popped.

Monolog 2:

$processor = $logger->popProcessor();

Monolog 3:

$logger->popProcessor();

Logger::API Constant

A new Logger::API constant has been added, which is an integer that will be incremented for every new API version. This can be useful for checking for compatibility with a certain version of Monolog.

Removed SwiftMailerHandler

The SwiftMailerHandler has been removed in Monolog 3. If your code uses it, migrate to SymfonyMailerHandler, which provides similar functionality using the Symfony Mailer component. The change typically involves updating your use statement and ensuring you have the symfony/mailer package installed instead of swiftmailer/swiftmailer. The API is largely compatible, but review the handler’s constructor arguments and configuration options as they differ slightly between the two implementations.

A Step-by-Step Upgrade Guide

Now that you are aware of the key changes, let’s walk through the upgrade process step-by-step.

  1. Update your composer.json file:

    Open your composer.json file and change the version constraint for monolog/monolog to ^3.0.

    {
        "require": {
            "monolog/monolog": "^3.0"
        }
    }
  2. Run composer update:

    Run the following command in your terminal to update the Monolog package:

    composer update monolog/monolog
  3. Update your code:

    Now you need to update your code to be compatible with Monolog 3. The changes you need to make will depend on how you are using Monolog. Here is a simple “before and after” example:

    Before (Monolog 2):

    use Monolog\Logger;
    use Monolog\Handler\StreamHandler;
    use Monolog\Processor\WebProcessor;
    
    $logger = new Logger('my_logger');
    $logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
    $logger->pushProcessor(new WebProcessor());
    
    $logger->warning('This is a warning.');

    After (Monolog 3):

    use Monolog\Logger;
    use Monolog\Handler\StreamHandler;
    use Monolog\Processor\WebProcessor;
    
    $logger = new Logger('my_logger');
    $logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
    $logger->pushProcessor(new WebProcessor());
    
    $logger->warning('This is a warning.');

    In this simple case, no code changes are needed. However, if you are using more advanced features, you will need to refer to the changelog and update your code accordingly.

  4. Run your tests:

    Once you have updated your code, run your test suite to make sure that everything is still working as expected.

Post-Upgrade Verification

After completing the code changes and running your tests, you should perform a thorough verification:

  1. Run your automated test suite. Ensure all tests pass. If any Monolog-related tests fail, investigate and fix before proceeding.

  2. Manual smoke tests. In your staging environment, generate log messages at various levels (debug, info, warning, error) and verify they appear in the expected destinations (files, syslog, external services). Confirm that context data (e.g., user identifiers, request IDs) is captured correctly.

  3. Check for deprecation notices. Monolog 3 may emit deprecation warnings if old constants are still used. Scan your logs for “deprecated” messages and address the underlying code.

  4. Performance sanity check. Monitor the application’s memory usage and request latency. The switch to LogRecord objects should not cause significant regressions, but it’s worth verifying in high-load scenarios.

  5. Prepare for deployment. Once you’re satisfied, commit your changes and tag a release. Before deploying to production, ensure you have a rollback plan (e.g., keep your previous composer.lock and a git tag). After deployment, monitor logs closely for the first few hours to catch any missed issues.

If you encounter problems during verification, consult the troubleshooting section below for guidance on common pitfalls.

Troubleshooting Common Issues

Even with careful preparation, you may encounter issues during the upgrade. This section helps you diagnose and resolve the most common problems.

”Class not found” Errors

Symptom: You see errors like “Class ‘Monolog\Handler\SomeHandler’ not found” or “Class ‘Monolog\Level’ not found”.

Likely causes:

  • The class has been moved to a different namespace in Monolog 3.
  • You haven’t updated your use statements to import the new class locations.
  • Composer’s autoloader hasn’t been regenerated after the upgrade.

Solutions:

First, verify your composer dependencies are correctly installed:

$ composer dump-autoload -o

If that doesn’t resolve the issue, check the Monolog changelog for the specific class. Some classes were renamed or moved. For example, Monolog\Level is a new enum class that replaced the integer constants. If you see “Class ‘Monolog\Level’ not found”, verify you have Monolog 3 installed:

$ composer show monolog/monolog | grep versions

You should see something like versions : * 3.x.x.

If you use a class that was removed (like SwiftMailerHandler), you’ll need to migrate to the replacement (SymfonyMailerHandler) or find an alternative.

One may wonder: “Why does autoload sometimes fail after upgrade?” The answer is typically that the Composer autoloader cache still references the old class map. Running composer dump-autoload regenerates it.

”Method not found” Errors

Symptom: Fatal error reporting that a method you’re calling on a Monolog class no longer exists.

Common scenarios:

  • pushProcessor or popProcessor return type changed. In Monolog 2, popProcessor() returned the popped processor. In Monolog 3, it returns $this (the Logger instance). If your code relies on the return value, adjust accordingly:

    // Monolog 2 (returned the processor)
    $processor = $logger->popProcessor();
    
    // Monolog 3 (returns the logger)
    $logger->popProcessor();
    // No return value to capture
  • Handler constructor signature changes. Some handlers may have different constructor arguments. Double-check the Monolog 3 documentation for the handler you’re using.

  • Processor signature changes. Custom processors may need to accept a LogRecord object instead of an array. If you have a custom processor, update its signature:

    // Monolog 2
    function(array $record) {
        // modify $record
        return $record;
    }
    
    // Monolog 3
    function(LogRecord $record) {
        // You can still access array elements via ArrayAccess, or use properties
        $record->context['foo'] = 'bar';
        return $record;
    }

If you encounter a missing method that you’re sure should exist, check whether the method was deprecated and removed across versions. The Monolog CHANGELOG.md lists all removed methods.

Type Errors (PHP 8.1 Strict Types)

Symptom: TypeError exceptions, such as:

TypeError: Monolog\Handler\StreamHandler::__construct(): Argument #2 ($level) must be of type Monolog\Level|int, int given

or

TypeError: Argument 1 passed to MyCustomProcessor::__invoke() must be an instance of Monolog\LogRecord, array given

These errors occur because Monolog 3 leverages PHP 8.1’s native type declarations, and your code may be passing incorrect types.

Resolving level type errors:

In Monolog 2, you could pass integer constants like Logger::WARNING to handler constructors. Those integers still work (for backwards compatibility), but if you see a TypeError about Monolog\Level|int, it may be because you’re passing an invalid integer or a string. Ensure you’re using either:

  • One of the Monolog\Level enum values: Level::Warning, Level::Error, etc.
  • Or a valid integer level (e.g., 200 for warning, 400 for error). Though integers are deprecated, they still work in many contexts.

If you use custom level names, you may need to adjust.

Resolving LogRecord type errors:

Custom processors and handlers that expect an array need to accept LogRecord instead. The LogRecord implements ArrayAccess, so accessing $record['context'] still works; however, the type hint must be LogRecord or array|LogRecord if you need to support both versions. For a clean Monolog 3 codebase, change the type hint to LogRecord and use object property access.

For example:

// Before (Monolog 2)
public function __invoke(array $record): array
{
    $record['extra']['source'] = 'myapp';
    return $record;
}

// After (Monolog 3)
use Monolog\LogRecord;

public function __invoke(LogRecord $record): LogRecord
{
    $record->extra['source'] = 'myapp';
    return $record;
}

Mixed-version support:

If you need to support both Monolog 2 and 3 in the same codebase (e.g., during a gradual rollout), you can use union types:

public function __invoke(array|LogRecord $record): array|LogRecord
{
    // Handle both
    if ($record instanceof LogRecord) {
        $record->extra['source'] = 'myapp';
    } else {
        $record['extra']['source'] = 'myapp';
    }
    return $record;
}

But this approach adds complexity; ideally, you’ll migrate fully to Monolog 3.

Missing Monolog\Level Class

If you see “Class ‘Monolog\Level’ not found”, verify that you have Monolog version 3.x installed. The Level enum was introduced in Monolog 3. If you are still on Monolog 2.x, you’ll need to upgrade the package first. Check your composer.lock:

$ grep monolog composer.lock

If it shows version 2.x, run composer update monolog/monolog. If you’ve already updated but still see this error, clear Composer’s autoloader cache:

$ composer dump-autoload -o

Unexpected Behavior After Upgrade

If after upgrading everything appears to work but you notice logs are missing or formatted differently, consider these checks:

  • Handler configuration: Verify that your handler levels and filters are still in place. The level semantics are similar, but if you used integer levels directly, they might not map exactly to the Level enum values. Use Level enum values to avoid confusion.

  • Processor order: The pushProcessor and popProcessor return values changed. If your code relied on chaining, adjust.

  • LogRecord handling: Some handlers may modify the record. Be aware that changes in processors can affect subsequent processors and the final log output.

  • Third-party integrations: If you use a framework’s logging integration (e.g., Symfony’s MonologBundle, Laravel’s logging manager), check its documentation for Monolog 3 compatibility. You may need to upgrade the framework package as well.

Verifying the Upgrade

After resolving any errors, run your test suite. Additionally, perform these manual checks in a staging environment:

  1. Generate test log messages at various levels (debug, info, warning, error) and confirm they appear in your log storage (files, syslog, external service).
  2. Check that context data (e.g., request IDs, user IDs) is included as expected.
  3. If you use log aggregation tools (ELK, Graylog, Datadog), confirm that log ingestion continues without errors.
  4. Monitor the application for a period to ensure no silent failures occur.

If you discover missed compatibility issues after deploying to production, monitor your logs closely for errors and be prepared to roll back using your previously saved composer.lock and tagged release.

By systematically addressing these common trouble spots, you can confidently complete your upgrade to Monolog 3.

Conclusion

Upgrading your dependencies is a crucial part of maintaining a healthy and secure application. While the upgrade process can sometimes be challenging, it is well worth the effort. By following the steps in this guide, you can ensure a smooth transition to Monolog 3 and take advantage of all the new features and improvements it has to offer.

Have you recently upgraded a project to Monolog 3? Share your experience in the comments below!

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming