DEV Community

Magevanta
Magevanta

Posted on • Edited on • Originally published at magevanta.com

Magento 2 Logging Best Practices: Performance, Structure & Debugging

Magento 2 Logging Best Practices: Performance, Structure & Debugging

Logging is the silent backbone of every Magento 2 store. When something breaks — a failed order, a stuck cron, a plugin throwing exceptions — logs are your first stop. But few developers treat logging as seriously as caching or indexing.

Poor logging habits cause real damage: debug logs growing to gigabytes in production, I/O bottlenecks from channel spam, critical errors buried in noise, and disk exhaustion taking down entire servers. This guide walks through how Magento 2 logging actually works, how to configure it properly, and how to build habits that keep logs useful without killing performance.

How Magento 2 Logging Works

Magento 2 uses Monolog — the PHP logging standard — as its PSR-3 logger implementation. Monolog sends log messages through channels (categories) and handlers (destinations). The entire system is configured in app/etc/di.xml and overridden per environment in env.php.

The core channels you'll find in a standard Magento 2 installation:

  • system — General system messages (var/log/system.log)
  • exception — Uncaught exceptions and fatal errors (var/log/exception.log)
  • debug — Debug-level output, disabled by default in production (var/log/debug.log)
  • db — Database queries and connection issues (var/log/db.log)
  • payment — Payment gateway communication (var/log/payment.log)
  • checkout — Checkout flow events (var/log/checkout.log)
  • setup — Installation and upgrade logs (var/log/setup.log)
  • crontab — Cron job execution logs (var/log/cron.log)
  • main — The catch-all channel used by \Magento\Framework\Logger\Monolog

Each channel maps to a handler that writes to var/log/<channel>.log. The magic happens in how you configure these channels per environment.

The Performance Cost of Logging

Here's the uncomfortable truth: every log line is a synchronous disk write. On a busy store with debug logging enabled, a single page request can generate 50+ log entries. Multiply that by thousands of requests per minute, and you're looking at:

  • Disk I/O contention — Log writes compete with MySQL, Redis, and Varnish for disk throughput
  • Disk space exhaustion — A debug log can grow 500MB+ per hour under moderate traffic
  • PHP execution time — Each ->info() or ->debug() call adds latency, even if you think nobody reads it
  • GC pressure — Large log files slow down log rotation and archival

I've seen stores where disabling debug logging alone reduced response time by 200ms. That's a bigger win than most caching optimizations.

Configuring Log Levels Per Environment

The single most important rule: production should never log at debug level. Magento 2's deploy modes handle this partially, but you should be explicit.

Production (env.php)

'log' => [
    'Monolog\Logger' => [
        'handlers' => [
            'system' => [
                'type' => 'stream',
                'path' => '/var/log/magento/system.log',
                'level' => 'warning'
            ],
            'exception' => [
                'type' => 'stream',
                'path' => '/var/log/magento/exception.log',
                'level' => 'error'
            ],
            'debug' => [
                'type' => 'stream',
                'path' => '/var/log/magento/debug.log',
                'level' => 'emergency' // effectively disabled
            ]
        ]
    ]
]
Enter fullscreen mode Exit fullscreen mode

By setting debug level to emergency, only the most critical messages pass through. The warning level on the system channel captures warnings, errors, and criticals — everything you need to investigate incidents.

Staging

Staging can afford info level on the system channel and debug level enabled — you need to reproduce issues before they hit production.

Development

In development, crank everything to debug or even info:

'log' => [
    'Monolog\Logger' => [
        'handlers' => [
            'system' => [
                'type' => 'stream',
                'path' => 'var/log/system.log',
                'level' => 'debug'
            ],
            'debug' => [
                'type' => 'stream',
                'path' => 'var/log/debug.log',
                'level' => 'debug'
            ]
        ]
    ]
]
Enter fullscreen mode Exit fullscreen mode

Log Rotation: Non-Negotiable

If you're not rotating logs, you're one bad weekend away from a full disk. Magento 2 doesn't handle rotation itself — use logrotate at the OS level.

Create /etc/logrotate.d/magento:

/var/www/magento/var/log/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}
Enter fullscreen mode Exit fullscreen mode

Key settings:

  • copytruncate — Copies the log file, then truncates the original. This avoids the need to signal Magento to reopen file handles, which can cause issues with long-running PHP processes.
  • rotate 14 — Keep two weeks of logs. Adjust based on your compliance requirements.
  • compress — Gzip-compress old logs to save space.
  • daily — Run every day. For high-traffic stores, consider hourly on the debug log specifically.

Check your rotation is actually running:

logrotate -d /etc/logrotate.d/magento
Enter fullscreen mode Exit fullscreen mode

Writing Logs in Custom Modules

When you build custom modules, never use ObjectManager to grab a logger. Inject the proper logger interface instead:

use Magento\Framework\Logger\Monolog;

class MyService
{
    private Monolog $logger;

    public function __construct(Monolog $logger)
    {
        $this->logger = $logger;
    }

    public function processOrder($order)
    {
        try {
            // ... logic ...
        } catch (\Exception $e) {
            $this->logger->error(
                'Order processing failed for order #{orderId}: {message}',
                [
                    'orderId' => $order->getIncrementId(),
                    'message' => $e->getMessage(),
                    'exception' => $e
                ]
            );
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Use PSR-3 Placeholder Interpolation

Always use curly-brace placeholders instead of string concatenation:

// GOOD — placeholder interpolation
$this->logger->info('Synced {count} products from ERP', ['count' => $productCount]);

// BAD — string concatenation evaluated even if log level is filtered
$this->logger->info('Synced ' . $productCount . ' products from ERP');

// BAD — sprintf inside log call
$this->logger->info(sprintf('Synced %d products', $productCount));
Enter fullscreen mode Exit fullscreen mode

Placeholders are only interpolated if the log message actually passes the level filter. String concatenation is always evaluated, even when the message gets discarded. On high-traffic stores, this difference matters.

Avoid Logging in Loops

// BAD — writes 100,000 log lines during a full catalog sync
foreach ($products as $product) {
    $this->logger->info('Processing product SKU: ' . $product->getSku());
    $this->syncProduct($product);
}

// GOOD — log a summary, use a timer, flag outliers
$startTime = microtime(true);
$processed = 0;
$errors = [];

foreach ($products as $product) {
    try {
        $this->syncProduct($product);
        $processed++;
    } catch (\Exception $e) {
        $errors[] = ['sku' => $product->getSku(), 'error' => $e->getMessage()];
    }
}

$this->logger->info('Catalog sync completed: {processed} products in {seconds}s, {errors} errors', [
    'processed' => $processed,
    'seconds' => round(microtime(true) - $startTime, 2),
    'errors' => count($errors)
]);

if (count($errors) > 0) {
    $this->logger->error('Catalog sync had {count} errors. First few: {details}', [
        'count' => count($errors),
        'details' => json_encode(array_slice($errors, 0, 5))
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Magento 2's Hidden Log Spam

Several core subsystems log aggressively in debug mode. The worst offenders:

  1. \Magento\Framework\DB\Adapter\Pdo\Mysql — Logs every query in debug mode. On a catalog page with 200 queries, this means 200 log lines per request.
  2. \Magento\Framework\Event\Manager — Logs every dispatched event. A single request can dispatch 50+ events.
  3. \Magento\Framework\App\Http — Logs plugin execution order and layout generation.
  4. \Magento\Framework\View\Layout — Logs every block render.

If you enable debug logging, you immediately get a firehose. The fix: create a custom handler that filters debug messages for specific channels only.

// app/code/Vendor/Logging/Model/SelectiveDebugHandler.php
namespace Vendor\Logging\Model;

use Monolog\Handler\StreamHandler;
use Monolog\Logger;

class SelectiveDebugHandler extends StreamHandler
{
    private array $allowedChannels = ['payment', 'checkout', 'custom_api'];

    public function isHandling(array $record): bool
    {
        if ($record['level'] >= Logger::INFO) {
            return parent::isHandling($record);
        }
        // Only allow debug-level for specific channels
        return in_array($record['channel'] ?? '', $this->allowedChannels)
            && parent::isHandling($record);
    }
}
Enter fullscreen mode Exit fullscreen mode

Register this handler in di.xml and you get full debug logging for payment flows without the database query firehose.

Structured Logging for Serious Debugging

Plain text logs are fine for "what happened at 3 AM?" investigations, but for any store doing serious volume, structured logging (JSON format) is a game-changer. It lets you pipe logs into Elasticsearch, Loki, or Datadog and run actual queries.

Magento 2 supports custom Monolog formatters. Register a JsonFormatter handler:

// app/code/Vendor/Logging/etc/di.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Monolog\Logger">
        <arguments>
            <argument name="handlers" xsi:type="array">
                <item name="json" xsi:type="object">
                    Vendor\Logging\Model\JsonLogHandler
                </item>
            </argument>
        </argument>
    </type>
</type>
Enter fullscreen mode Exit fullscreen mode
// app/code/Vendor/Logging/Model/JsonLogHandler.php
namespace Vendor\Logging\Model;

use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;

class JsonLogHandler extends StreamHandler
{
    public function __construct()
    {
        parent::__construct('var/log/structured.log', Logger::WARNING);
        $this->setFormatter(new JsonFormatter());
    }
}
Enter fullscreen mode Exit fullscreen mode

Now every log entry above the warning threshold is written as structured JSON:

{"message":"Order #100234 processing failed: Payment gateway timeout","level":400,"level_name":"ERROR","channel":"main","datetime":"2026-08-02T09:15:23.123+0000","context":{"orderId":"100234","reason":"timeout"}}
Enter fullscreen mode Exit fullscreen mode

Centralized log viewers can then filter, search, and alert on structured fields.

Centralized Logging: The Next Step

For multi-server deployments, writing logs to local files is insufficient. You need centralized logging — shipping logs to a dedicated aggregator.

Option 1: Filebeat + Elasticsearch + Kibana (ELK)

Install Filebeat on each Magento server, tail the log files, and ship to Elasticsearch. Kibana gives you a dashboard to search and visualize errors across all servers.

# /etc/filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    paths:
      - /var/www/magento/var/log/*.log
    fields:
      environment: production
      application: magento
    fields_under_root: true

output.elasticsearch:
  hosts: ["logstash.internal:9200"]
Enter fullscreen mode Exit fullscreen mode

Option 2: Vector or Fluent Bit

Lighter than Filebeat and supports filtering, enrichment, and routing at the edge. For Magento stores running in containers, Fluent Bit as a DaemonSet is the standard choice.

Option 3: SaaS (Datadog, New Relic, Sentry)

If you're already using a monitoring platform, ship logs there directly. Datadog has a Monolog handler package that integrates cleanly:

composer require datadog/dd-trace
Enter fullscreen mode Exit fullscreen mode

Configure the Monolog handler to send logs to Datadog's intake endpoint, and you get automatic correlation between APM traces, infrastructure metrics, and log entries.

Debugging Tips: Finding the Needle

When production is broken and you need to find the relevant log entry fast:

1. Filter by Time Window

# All errors in a 10-minute window
awk '/2026-08-02 11:0[0-9]/' var/log/system.log | grep -i error

# Errors containing a specific keyword
grep "payment" var/log/exception.log | tail -20
Enter fullscreen mode Exit fullscreen mode

2. Correlate Across Channels

An exception might be logged in exception.log while the root cause appears in db.log or system.log seconds earlier:

# Find the timestamp range of an exception
head -1 var/log/exception.log
# Then search other logs in that window
grep "2026-08-02 11:03:" var/log/system.log var/log/db.log
Enter fullscreen mode Exit fullscreen mode

3. Enable Temporary Targeted Debug Logging

Instead of enabling global debug mode, inject a targeted logger for just the subsystem you're investigating:

// In your di.xml (temporary)
<type name="Magento\Framework\DB\Adapter\Pdo\Mysql">
    <arguments>
        <argument name="logger" xsi:type="object">Vendor\Logging\Model\QueryDebugLogger</argument>
    </argument>
</type>
Enter fullscreen mode Exit fullscreen mode

Remove it after debugging — never commit temporary debug loggers.

A Practical Logging Checklist

Before going live with any Magento 2 store, run through this checklist:

  1. Deploy mode is productionbin/magento deploy:mode:set production
  2. Debug logging disabled — Check env.php level and MAGE_DEBUG_SHOW_ARGS
  3. Logrotate configured and testedlogrotate -d /etc/logrotate.d/magento
  4. Disk space monitoring — Monitor /var/log/magento/ disk usage
  5. Custom module logging uses placeholders — No string concatenation or sprintf
  6. No debug logging inside loops — Summary > per-item
  7. Exception log checked dailyvar/log/exception.log should be empty or near-empty on a healthy store
  8. Log directory permissions correct — Web server user needs write access, nobody else
  9. Payment and checkout channels writable — These are your most critical logs
  10. Centralized logging setup — For multi-server, ship logs to ELK/Datadog/Sentry

Conclusion

Logging in Magento 2 is deceptively simple — call a logger, write to a file, done. But at scale, the difference between good and bad logging habits is the difference between a healthy store and one that crumbles under load. By using the right log levels per environment, rotating aggressively, writing structured logs for searchability, and avoiding the most common performance traps, you build a foundation that keeps your store observable without sacrificing speed.

The best logs are the ones you never have to read — because the system is well-behaved. But when things go wrong, you'll be glad you set this up properly.

Top comments (0)