ecourty / logger-bundle
A Symfony bundle providing a structured, context-aware logger with pluggable per-project parameter processors.
Package info
github.com/EdouardCourty/logger-bundle
Type:symfony-bundle
pkg:composer/ecourty/logger-bundle
Requires
- php: >=8.3
- psr/log: ^3.0
- symfony/config: ^7.0|^8.0
- symfony/dependency-injection: ^7.0|^8.0
- symfony/http-kernel: ^7.0|^8.0
- symfony/monolog-bundle: ^3.0|^4.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.71
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.0|^13.0
- symfony/framework-bundle: ^7.0|^8.0
- symfony/var-dumper: ^7.2|^8.0
- symfony/yaml: ^7.0|^8.0
Suggests
- phpstan/phpstan: To use the bundle's DisallowLoggerInterfaceRule
Provides
None
Conflicts
None
Replaces
None
README
A Symfony bundle providing a single, reusable Logger service — structured, context-typed
logging with pluggable, per-project parameter processors, instead of hand-rolling the same
Logger/LogParameterProcessorInterface pattern in every project.
Table of Contents
Requirements
- PHP ≥ 8.3
- Symfony ≥ 7.0
symfony/monolog-bundleregistered in your kernel
Installation
composer require ecourty/logger-bundle
Register the bundle in config/bundles.php (if not using Symfony Flex):
return [ // ... Ecourty\LoggerBundle\LoggerBundle::class => ['all' => true], ];
Declare the Monolog channel Logger will write to (the bundle reuses an existing channel,
it never creates one for you — pick any name that fits your project):
# config/packages/monolog.yaml monolog: channels: [business] # must match logger.channel below
Core Features
- 8-level PSR-3 API —
emergencythroughdebug, each call typed with a\BackedEnum $contextand anarray $parameters - Structured context tagging — every message is formatted as
"{$context->value} - {$message}" - Pluggable parameter processors — any value passed in
$parametersis run through the first registeredLogParameterProcessorInterfaceimplementation that supports it; an exception processor ships by default, add your own for any domain type - Reuses an existing Monolog channel — bind
Loggerto any channel you declare yourself; the bundle never touches yourmonolog.yaml - Optional message prefixing — identify the app in aggregated, multi-project logs
- Optional PHPStan enforcement — forbid direct
Psr\Log\LoggerInterfaceinjection so the pattern stays mandatory, not opt-in
Configuration
# config/packages/logger.yaml logger: channel: business # required — any channel name you've declared in monolog.yaml message_prefix: ~ # default enable_builtin_processors: true # default
| Option | Type | Default | Description |
|---|---|---|---|
channel |
string |
(required) | Monolog channel the Logger service writes to. Must be declared under monolog.channels in your own config. |
message_prefix |
string | null |
null |
Prepended to every message on this channel (e.g. "PMS - "). Disabled when null. |
enable_builtin_processors |
bool |
true |
Register the bundle's own default processors (currently: ThrowableLogParameterProcessor). Set to false to fully replace them. |
Usage
Logging
Autowire Ecourty\LoggerBundle\Service\Logger like any other service:
use Ecourty\LoggerBundle\Service\Logger; final class PlaceOrderHandler { public function __construct( private readonly Logger $logger, ) { } public function handle(Order $order): void { $this->logger->info(OrderLogContext::Created, 'Order placed', [ 'orderId' => $order->getId(), ]); } }
All 8 PSR-3 levels are available: emergency, alert, critical, error, warning,
notice, info, debug.
Defining Log Contexts
Log contexts are plain \BackedEnum cases — no bundle interface to implement. Define one
enum per domain, alongside that domain's own code:
namespace App\Order\Enum; enum OrderLogContext: string { case Created = 'OrderCreated'; case PaymentFailed = 'OrderPaymentFailed'; }
Writing a Custom Parameter Processor
Implement Ecourty\LoggerBundle\Contract\LogParameterProcessorInterface. It's
auto-registered — no service configuration needed:
namespace App\Security\Logger; use App\Security\User; use Ecourty\LoggerBundle\Contract\LogParameterProcessorInterface; final class UserLogParameterProcessor implements LogParameterProcessorInterface { public function supports(mixed $value): bool { return $value instanceof User; } public function process(mixed $value): mixed { return [ 'id' => $value->getId(), 'email' => $value->getEmail(), ]; } }
$this->logger->warning(SecurityLogContext::LoginFailed, 'Login failed', [ 'user' => $user, // serialized by UserLogParameterProcessor ]);
Tip: processors are tried in registration order; the first one whose
supports()returnstruewins. The bundledThrowableLogParameterProcessorhandles any\Throwable— setenable_builtin_processors: falseif you want to replace it with your own.
Message Prefixing
Set message_prefix to identify which application a log line came from once several
projects ship to the same aggregator:
logger: message_prefix: 'PMS - '
$this->logger->error(OrderLogContext::PaymentFailed, 'Payment declined'); // → "PMS - OrderPaymentFailed - Payment declined"
Enforcing the Pattern with PHPStan
Register the bundled rule in your own phpstan.neon to forbid injecting
Psr\Log\LoggerInterface directly anywhere in your codebase:
rules: - Ecourty\LoggerBundle\PHPStan\DisallowLoggerInterfaceRule
Development
composer install # Run all tests composer test # Run specific test suites composer test-unit composer test-integration composer test-functional # Static analysis (PHPStan, level max) composer phpstan # Code style (PHP CS Fixer) composer cs-fix # fix composer cs-check # dry-run check # Full QA pipeline (PHPStan + CS check + tests) composer qa
See AGENTS.md for architecture details and contribution guidelines.
License
This bundle is released under the MIT License.