Search by

ecourty / logger-bundle

ecourty

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

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-09-24 14:24 UTC

This package is auto-updated.

Last update: 2026-09-24 14:40:33 UTC


README

CI

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-bundle registered 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 — emergency through debug, each call typed with a \BackedEnum $context and an array $parameters
  • Structured context tagging — every message is formatted as "{$context->value} - {$message}"
  • Pluggable parameter processors — any value passed in $parameters is run through the first registered LogParameterProcessorInterface implementation that supports it; an exception processor ships by default, add your own for any domain type
  • Reuses an existing Monolog channel — bind Logger to any channel you declare yourself; the bundle never touches your monolog.yaml
  • Optional message prefixing — identify the app in aggregated, multi-project logs
  • Optional PHPStan enforcement — forbid direct Psr\Log\LoggerInterface injection 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() returns true wins. The bundled ThrowableLogParameterProcessor handles any \Throwable — set enable_builtin_processors: false if 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.