Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@
/.php-cs-fixer.cache

.idea/
docker-compose.yml
.junie/
docker-compose.yml
.aiignore
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ The bundle works out-of-the-box with default configuration.

## Quick Start

### Basic Usage

Basic Usage
Once installed, the bundle automatically:

1. Reads the X-Correlation-ID header from incoming requests
Expand Down Expand Up @@ -151,7 +148,7 @@ class UserController extends AbstractController
#[Route('/api/users', methods: ['GET'])]
public function list(): JsonResponse
{
$$correlationId = $$this->correlationIdStorage->get();
$correlationId = $this->correlationIdStorage->get();

$this->logger->info('Fetching users', [
'correlation_id' => $correlationId,
Expand Down Expand Up @@ -180,6 +177,7 @@ $this->correlationIdStorage->clear();
```

## Monolog Integration

### Automatic Logging
When Monolog integration is enabled (default), the correlation ID is automatically added to all log entries in the `extra` field.
**Example log output:**
Expand Down Expand Up @@ -212,6 +210,33 @@ composer require monolog/monolog
```
If Monolog is not installed, the integration is automatically disabled.

## CLI Integration

When CLI integration is enabled (default), the bundle manages correlation IDs for Symfony Console commands.

### Global Option
If `cli.allow_option` is `true`, a global `--correlation-id` option is added to all commands:

```bash
php bin/console app:my-command --correlation-id=custom-id-123
```

### Automatic ID Generation
If no option is provided, an ID is automatically generated using the configured generator and prefixed with `cli.prefix` (default: `CLI-`):

**Example output for a generated ID:** `CLI-550e8400-e29b-41d4-a716-446655440000`

### Access in Commands
You can access the ID in your commands just like in controllers:

```php
protected function execute(InputInterface $input, OutputInterface $output): int
{
$correlationId = $this->correlationIdStorage->get();
// ...
}
```

## Advanced Usage

### Custom ID Generator
Expand Down Expand Up @@ -259,4 +284,4 @@ vendor/bin/phpunit --coverage-html build/coverage
This bundle is released under the MIT License.

## Contributing
Contributions are welcome! Please submit a Pull Request.
Contributions are welcome! Please submit a Pull Request.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/uid": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0"
"symfony/yaml": "^6.4|^7.0",
"symfony/console": "^6.4|^7.0"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
Expand Down
11 changes: 11 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,16 @@ services:
arguments:
$storage: '@MdavidDev\SymfonyCorrelationIdBundle\Service\CorrelationIdStorage'
$headerName: '%correlation_id.header_name%'
tags:
- { name: kernel.event_subscriber }

# Console Listener
MdavidDev\SymfonyCorrelationIdBundle\EventListener\ConsoleListener:
arguments:
$storage: '@MdavidDev\SymfonyCorrelationIdBundle\Service\CorrelationIdStorage'
$generator: '@MdavidDev\SymfonyCorrelationIdBundle\Service\Generator\CorrelationIdGeneratorInterface'
$validator: '@MdavidDev\SymfonyCorrelationIdBundle\Validator\CorrelationIdValidator'
$prefix: '%correlation_id.cli.prefix%'
$allowOption: '%correlation_id.cli.allow_option%'
tags:
- { name: kernel.event_subscriber }
35 changes: 35 additions & 0 deletions src/Console/ApplicationDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace MdavidDev\SymfonyCorrelationIdBundle\Console;

use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

final class ApplicationDecorator extends BaseApplication
{

public function __construct(BaseApplication $application)
{
parent::__construct($application->getName(), $application->getVersion());

$this->setCatchExceptions($application->areExceptionsCaught());
$this->setAutoExit($application->isAutoExitEnabled());
}

protected function getDefaultInputDefinition(): InputDefinition
{
$definition = parent::getDefaultInputDefinition();

$definition->addOption(new InputOption(
'correlation-id',
null,
InputOption::VALUE_REQUIRED,
'Correlation ID for this command execution'
));

return $definition;
}
}
32 changes: 32 additions & 0 deletions src/DependencyInjection/Compiler/ConsoleCommandCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace MdavidDev\SymfonyCorrelationIdBundle\DependencyInjection\Compiler;

use MdavidDev\SymfonyCorrelationIdBundle\Console\ApplicationDecorator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class ConsoleCommandCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasParameter('correlation_id.cli.allow_option') || !$container->getParameter('correlation_id.cli.allow_option')) {
return;
}

if (!$container->hasDefinition('console.application')) {
return;
}

if (!$container->hasDefinition(ApplicationDecorator::class)) {
$container->register(ApplicationDecorator::class, ApplicationDecorator::class)
->setDecoratedService('console.application')
->setArguments([new Reference('.inner')])
->addMethodCall('setDispatcher', [new Reference('event_dispatcher')])
->setPublic(false);
}
}
}
12 changes: 1 addition & 11 deletions src/DependencyInjection/Compiler/MonologCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ class MonologCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
// Vérifier si Monolog est disponible
if (!$this->isMonologAvailable()) {
return;
}

// Vérifier si l'intégration Monolog est activée
if (!$container->hasParameter('correlation_id.monolog')) {
return;
}
Expand All @@ -29,10 +27,8 @@ public function process(ContainerBuilder $container): void
return;
}

// Enregistrer le processor
$this->registerProcessor($container, $monologConfig);

// Ajouter le processor à tous les loggers Monolog
$this->addProcessorToLoggers($container);
}

Expand All @@ -59,33 +55,27 @@ private function registerProcessor(ContainerBuilder $container, array $monologCo

private function addProcessorToLoggers(ContainerBuilder $container): void
{
// Chercher tous les services dont l'ID commence par "monolog.logger"
foreach ($container->getDefinitions() as $id => $definition) {
// Filtrer uniquement les vrais loggers Monolog
if (!str_starts_with($id, 'monolog.logger')) {
continue;
}

// Vérifier que c'est bien un logger Monolog
$class = $definition->getClass();
if ($class === null) {
$class = $id;
}

// Résoudre la classe si c'est un paramètre
if (str_starts_with($class, '%') && str_ends_with($class, '%')) {
$class = $container->getParameter(trim($class, '%'));
}

// Vérifier que c'est bien Monolog\Logger ou une sous-classe
if ($class !== 'Monolog\\Logger' && !is_subclass_of($class, 'Monolog\\Logger')) {
continue;
}

// Ajouter le processor
$definition->addMethodCall('pushProcessor', [
new Reference(CorrelationIdProcessor::class)
]);
}
}
}
}
Loading