lasso/multilog-bundle
Symfony bundle for managing multiple Monolog loggers in one application. Configure separate channels, handlers, and log files, then inject the logger you need so different services can write to independent logs without duplicating Monolog setup.
This is a 2014-era Symfony bundle, archived, with no dependents. Verify compatibility before writing any code — the realistic ceiling is PHP 5.6/7.0 and Symfony 2.3–3.0 with Monolog 1.x. If you are on Symfony 4+, stop here and jump to the migration pattern in the next section.
Where to look first (in the repo):
README.md — the only source of truth for the config key (likely lasso_multilog:) and the service naming scheme.DependencyInjection/Configuration.php + DependencyInjection/*Extension.php — the exact config tree. This tells you what a "logger" definition accepts (handlers, formatters, level).Resources/config/services.yml — the service IDs you will actually inject. Note whether the bundle uses a factory or a compiler pass.Logger/, Factory/, or Service/ — the runtime class and its public methods (expect debug/info/warning/error, or a PSR-3 passthrough to Monolog).Tests/ — the fastest executable spec for config quirks.Minimal install (Symfony 2.x):
composer require lasso/multilog-bundle:dev-master
// app/AppKernel.php
public function registerBundles()
{
return [
// ...
new Lasso\MultilogBundle\LassoMultilogBundle(),
];
}
First use case — two loggers for one service. The bundle's premise is that a single service (say orders) needs more than one log stream: an operational stream for errors/retries, and a business/audit stream for "order 123 placed". Configure both under one logical service name:
# app/config/config.yml
lasso_multilog:
loggers:
orders.ops:
level: warning
orders.audit:
level: info
formatter: json # or whatever the tree exposes
class OrderService
{
private $ops;
private $audit;
public function __construct($opsLogger, $auditLogger)
{
$this->ops = $opsLogger;
$this->audit = $auditLogger;
}
}
Confirm the concrete service IDs with php app/console debug:container lasso — do not guess them from the README alone; the naming is usually <bundle_alias>.logger.<key> but that is the single most common source of wiring failures in old bundles.
1. Inject the named logger, never the container. The bundle's value is that it hands you separate logger instances, so let DI do the work:
services:
app.order_service:
class: Acme\AppBundle\Service\OrderService
arguments:
- @lasso_multilog.logger.orders.ops
- @lasso_multilog.logger.orders.audit
2. Split by audience, not by severity. The orders.ops vs orders.audit split above is the pattern worth copying: ops logs go to a channel paged on-call, audit logs go to a file/queue that compliance reads. Same service, different consumers, different formatters and retention.
3. Wrap it behind a domain interface. Old logger objects are hard to fake in tests. Put an OrderAuditLogger interface in front and let the bundle-provided service be one adapter. That also makes the exit from the bundle cheap (see pattern 5).
4. Use the channel name as the join key. If the bundle supports a channel/name parameter, set it to the service ID (orders). Every line then carries the emitting service, which is exactly what you want when one service fans out to several streams.
5. Migration pattern — this is the day-to-day reality in 2024+. The bundle is a thin reimplementation of what Monolog now does natively. Replace it with real channels and named autowiring:
# Symfony 5/6/7 equivalent
monolog:
channels: ['orders_ops', 'orders_audit']
handlers:
orders_ops:
type: rotating_file
path: '%kernel.logs_dir%/orders_ops.log'
level: warning
channels: ['orders_ops']
orders_audit:
type: stream
path: '%kernel.logs_dir%/orders_audit.log'
level: info
channels: ['orders_audit']
final class OrderService
{
public function __construct(
private LoggerInterface $ordersOpsLogger,
private LoggerInterface $ordersAuditLogger,
) {}
}
That is ~15 lines of config and a _instanceof alias, versus a dependency on an unmaintained LGPL bundle. Budget the migration up front; retrofit later is more expensive than doing it first.
6. Testing. In config_test.yml, point every logger at Monolog's TestHandler (or a null handler) and assert on records. Do not assert on files on disk — the bundle's file-based output makes that brittle under parallel test runs.
config/bundles.php, @service YAML notation is deprecated, and container-aware base classes (ContainerAware, getContainer()) were removed in 4.0/5.0. Anything calling those will fatal.FormatterInterface::format() signatures changed (the array $record return type, StreamHandler constructor args, removal of addInfo-era internals). The bundle was written against Monolog 1; pinning monolog/monolog:^1.0 forces you off most current packages.src/, or you drag your application's licensing into the discussion. Get a legal read before forking.php app/console cache:clear --env=prod — logger instances are built at container compile time, so live config edits silently do nothing until the cache is rebuilt. Same class of bug if a compiler pass registered the logger services.lazy: true and use the monolog.logger tag rather than direct constructor injection.debug:container <id>, config:dump-reference lasso_multilog, then check that the file handler's directory exists and is writable (app/logs/ on Symfony 2, not var/log/). Silent logging failures are almost always a missing directory or a level threshold set higher than the records you are emitting.monolog.handler can usually be referenced from the bundle's config tree; a CompilerPass auto-tagging your domain services is a clean way to avoid hand-wiring every logger argument. Inspect the bundle for a LassoMultilogBundle::build() method before assuming neither exists.Psr\Log\LoggerInterface, never the bundle's concrete class, so the migration in pattern 5 is a config change rather than a refactor.How can I help you explore Laravel packages today?