Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Monolog Extra Bundle Laravel Package

hexanet/monolog-extra-bundle

Symfony bundle that extends Monolog with extra logging tools, making it easier to enrich log records with additional context and configure richer logging pipelines in your application. It integrates with Symfony's service configuration for straightforward setup and minimal boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a small, single-purpose Symfony integration: it registers Monolog processors (client IP, request context, response/status context) and a request/response logger so that a log line contains the HTTP context without you passing it around manually. It is a legacy package (last "real" feature work 2018-11-14, 3 stars, 0 dependents) that still receives occasional metadata/patch releases — most recently v3.0.2 — so treat it as something you adopt only when inheriting a Symfony 2.8/3.x/4.1-era app, not something you add to a new project.

Install and register

composer require hexanet/monolog-extra-bundle
  • Symfony ≤3.x: add new Hexanet\MonologExtraBundle\HexanetMonologExtraBundle() to AppKernel::registerBundles().
  • Symfony 4+: add it to config/bundles.php. There is no Flex recipe, so you register it by hand.

Pin the exact version in composer.json (e.g. "hexanet/monolog-extra-bundle": "3.0.2") rather than floating a caret constraint. The package ships patch releases that change the shape of log payload keys (see v3.0.2 below), and with no dependents there is no ecosystem pressure to keep such changes semver-major.

Where to look first

  1. README.md — the config example is short and is effectively the documentation.
  2. src/DependencyInjection/Configuration.php — this, not the README, is the source of truth for the config tree and its node names. Read it before writing YAML.
  3. src/Resources/config/services.* — shows the concrete service IDs and which monolog.processor tags the bundle adds. Verify with bin/console debug:container --tag=monolog.processor.
  4. CHANGELOG.md / the GitHub releases page — the notes are terse to the point of being cryptic ("Additions key is not 'nomalized' anymore"), so read them as "the shape of what lands in record['extra'] changed" and diff the actual processor source between tags before upgrading.

First use case: a dedicated request log

# config/packages/monolog.yaml
monolog:
    channels: ['request']
    handlers:
        request:
            type: stream
            path: '%kernel.logs_dir%/request.log'
            level: info
            channels: ['request']

Then, wherever you need it, inject the channel-specific logger rather than the generic one:

# config/services.yaml
services:
    _defaults:
        bind:
            Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
final class CheckoutController
{
    public function __construct(private LoggerInterface $requestLogger) {}

    public function __invoke(Request $request, Cart $cart): Response
    {
        $this->requestLogger->info('checkout.start', ['cart_id' => $cart->getId()]);

        // ...
    }
}

The point of the exercise: request.log lines are enriched with method/URI/route/client IP automatically, so a grep for checkout.start gives you the full HTTP context for free.

Implementation Patterns

Attach processors per-handler, not globally. The bundle's processors are ordinary Monolog processors tagged monolog.processor. Prefer scoping them with the channel attribute so that console/worker logs don't carry useless request context:

services:
    Hexanet\MonologExtraBundle\Processor\RequestProcessor:
        tags:
            - { name: monolog.processor, channel: request }

A global tag (no channel) is tempting but pollutes every record, including Messenger/CLI ones where there is no request.

Keep the request log "fail-only". The most common real-world wiring is a fingers_crossed handler: buffer request logs at debug level, only flush to disk when an error is logged. That gives you full request breadcrumbs on failure and near-zero noise on success:

monolog:
    handlers:
        request_buffer:
            type: fingers_crossed
            action_level: error
            handler: request_file
            channels: ['request']
        request_file:
            type: stream
            path: '%kernel.logs_dir%/request.log'
            level: debug
            channels: ['request']

Correlate across channels. Add a request-ID processor early (the bundle's extra processors are a convenient place to hang this) that writes extra.request_id, then include it in the line formatter so app.log and request.log can be joined:

monolog:
    handlers:
        main:
            type: stream
            path: '%kernel.logs_dir%/%kernel.environment%.log'
            formatter: monolog.formatter.line
services:
    monolog.formatter.line:
        class: Monolog\Formatter\LineFormatter
        arguments:
            - "[%%datetime%%] [%%level_name%%] [%%extra.request_id%%] %%channel%%: %%message%% %%context%%\n"

Because the extra keys are not guaranteed stable across releases, keep the formatter tolerant: emit %%extra%% as a whole in JSON, or supply explicit array keys, rather than hard-coding a single key name in a bracket template that renders empty when a key moves.

Log responses from a kernel.response listener, not from the controller. Response status and timing only exist after the kernel has handled the request. The bundle's request/response logger already listens there — if you need your own fields (duration, memory, route), add a listener at a low priority after the bundle's and use RequestStack for the request side.

Use named channels per surface. request, security, api as separate channels keeps channels: ['!event'] style exclusion lists manageable and lets you route the API channel to a different handler/file than the web one.

Structured extra over string messages. Whatever the bundle puts in record['extra'], consume it defensively: keys are stable in practice within a pinned version, but v3.0.2 proves they are not frozen — an "additions" key that used to be normalized no longer is. Treat any downstream parser (ELK, Loki, Sentry breadcrumbs) as needing a tolerance layer: normalize key casing and nesting yourself on ingest, or map to a fixed schema in your own processor rather than reading the bundle's keys directly.

Gotchas and Tips

  • Legacy and effectively unmaintained. Zero dependents and no real feature work since 2018. Its composer constraints target Symfony ~3.4/4.1 and Monolog 1.x. On Symfony 5+/Monolog 3 you will hit hard dependency conflicts; don't fight them, port the ~30 lines you actually use.
  • v3.0.2 changed payload shape (patch bump, behavioural change). "Additions key is not 'nomalized' anymore" means the extra/additions key is no longer normalised before being written into the record — casing, separators or nesting may now differ from what your dashboards and grep queries were built against. Before and after upgrading, log one request with -vvv and diff the raw line; then update your Logstash/Loki field mappings or your formatter. Do not assume the key name you saw on 3.0.1 survives.
  • Two LoggerInterfaces in the container. Autowiring Psr\Log\LoggerInterface does not resolve to the bundle's channel logger — you must use @monolog.logger.request (via bind or @ syntax) or a compiler-pass alias. This is the single most common "why is nothing in my request.log" bug.
  • getCurrentRequest() returns null in CLI, Messenger workers, and long-running commands. Any processor that assumes a request will fatal there. If you copy the processor code into your own app, guard with if (null === $request) return $record;.
  • Sub-requests fire kernel.request more than once. On Symfony 4.1+ use isMainRequest() (4.4+) / isMasterRequest() (older) or you will log fragments and ESI hits as separate entries.
  • Never log raw request bodies. Form data, Authorization headers, cookies and query strings routinely contain credentials and PII. If you enable body logging via the bundle config, add a redaction list (password, token, _csrf, authorization) and truncate payloads — an unbounded body in a Monolog record will also balloon your log volume.
  • Response bodies are often unavailable or expensive. StreamedResponse hasn't been generated when kernel.response fires, and BinaryFileResponse is pointless to capture. Log status, content-type and Content-Length instead.
  • kernel.response is not "response delivered." It fires before the server actually writes to the socket; a client disconnect after that point won't be reflected in your log.
  • Processors run on every record for the handler they're tagged to. Keep them allocation-cheap. RequestStack access is fine; a DB or HTTP call inside a processor is a latency bug that hides in plain sight.
  • Config typos fail silently-ish. A wrong root key means the extension never loads; run bin/console cache:clear after changing bundle configuration and confirm with debug:container --tag=monolog.processor that the tagged services exist.
  • Extension points you can rely on: (1) monolog.processor tag with a channel attribute — any __invoke(array $record): array works; (2) decorating monolog.logger.<channel>; (3) monolog.formatter for output shape. All three are stable Monolog contracts (unlike the bundle's own key naming), so the migration off this bundle is mechanical:
final class RequestContextProcessor
{
    public function __construct(private RequestStack $requests) {}

    public function __invoke(array $record): array
    {
        $request = $this->requests->getCurrentRequest();
        if ($request === null) {
            return $record;
        }

        $record['extra']['request'] = [
            'method' => $request->getMethod(),
            'uri'    => $request->getRequestUri(),
            'route'  => $request->attributes->get('_route'),
            'ip'     => $request->getClientIp(),
        ];

        return $record;
    }
}

That class replaces the bulk of the value this bundle provides, gives you full control over key naming (so a patch release can never silently reshape your log schema again), has no Symfony version ceiling, and is something you can unit test in isolation — which is the recommended exit path.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core