ayaou/command-logger-bundle
Symfony bundle that logs executed console commands with arguments, execution time, exit codes, and errors. Configure via attributes or wildcard patterns, view and filter logs with the command-logger:show command.
Install the package:
composer require ayaou/command-logger-bundle
Enable the bundle in config/bundles.php:
Ayaou\CommandLoggerBundle\AyaouCommandLoggerBundle::class => ['all' => true],
Configure logging in config/packages/command_logger.yaml:
command_logger:
enabled: true
Run migrations to create the command_log table:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Upgrading to v1.11.0+: output capture adds a column to
command_log. Re-rundoctrine:migrations:diff+migrateafter bumping the bundle, even if you never enable output capture.
(Optional, v1.11.0+) Enable output capture — off by default:
command_logger:
enabled: true
capture_output: true # opt-in flag; confirm exact key in the bundle README
Annotate a command to log its execution:
use Ayaou\CommandLoggerBundle\Attribute\CommandLogger;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'app:test-command')]
#[CommandLogger]
class TestCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Test command executed!');
return Command::SUCCESS;
}
}
Run the command:
php bin/console app:test-command
Verify logs:
php bin/console command-logger:show
With capture_output: true (v1.11.0+), the same command also records the Test command executed! line and command-logger:show renders it alongside the exit code — no more re-running a cron job just to see what it printed.
Use the commands array in command_logger.yaml to log specific commands without annotations:
command_logger:
commands:
- app:cron-job:*
- make:migration
- make:model
*) support partial command names (e.g., app:cron-job:* logs all app:cron-job-* commands).doctrine:migrations:migrate).Disable selective logging and enable global logging:
command_logger:
enabled: true
commands: [] # Empty array logs all commands
For commands without annotations, ensure they’re listed in the commands config. For new commands, prefer the #[CommandLogger] attribute:
#[AsCommand(name: 'app:deploy')]
#[CommandLogger]
class DeployCommand extends Command { ... }
Output capture is opt-in and applies to commands that are already being watched (attribute or config list) — it is not a second, independent selection mechanism.
# config/packages/command_logger.yaml
command_logger:
enabled: true
capture_output: true
commands:
- app:cron-job:*
Typical workflows this unlocks:
app:deploy summary output as evidence of what shipped.app:import run emitted the night before.Where output is captured, view it per record:
php bin/console command-logger:show --id=123
Enable it narrowly. Point capture_output at a small commands list (cron jobs, deploys, imports) rather than globally — see the storage and secrets pitfalls below.
Use the CommandLoggerRepository service to fetch logs in custom logic:
use Ayaou\CommandLoggerBundle\Repository\CommandLoggerRepository;
class MyService {
public function __construct(
private CommandLoggerRepository $loggerRepo
) {}
public function getFailedDeployments(): array {
return $this->loggerRepo->findBy([
'commandName' => 'app:deploy',
'exitCode' => Command::FAILURE
]);
}
}
Failures now carry the captured output too, so you can build a "last error output" widget for an admin dashboard straight from the repository result.
command-logger:purge periodically (e.g., weekly):
0 0 * * 0 php bin/console command-logger:purge --threshold=30
php bin/console command-logger:purge --threshold=90
php bin/console command-logger:show --error
php bin/console command-logger:show --id=123
#[CommandLogger] override config-based logging.commands config to avoid duplicates.*) match suffixes only (e.g., app:cron-job:* matches app:cron-job:daily but not app:cron-job-backup).commands:
- app:cron-job:*
- app:cron-*
composer.json if using Symfony 8:
composer require doctrine/orm:^3.0
capture_output: true against an un-migrated database will fail writes (or silently drop output, depending on your Doctrine setup).doctrine:migrations:diff + migrate as part of the bundle upgrade, then flip the flag. In CI, deploy the migration and the config change in that order.$output->writeln() is now at rest in the database, readable by anyone with DB access or the command-logger:show CLI.capture_output scoped to a handful of commands, never global.command_log table as sensitive data in your retention/GDPR policy, and shorten purge thresholds accordingly.capture_output: true does not opt commands in. If a command isn't in commands and isn't annotated #[CommandLogger], no row exists and therefore no output exists.enabled: true → confirm the command is watched (attribute or exact config name) → confirm capture_output: true → confirm the record was written after the upgrade.exit(), killed process) may log a short or empty output even though the failure is recorded. The exit code is still the reliable signal.exitCode first. For hard fatals, keep your normal Monolog/Sentry pipeline alongside this bundle rather than replacing it.-v/-vvv, progress bars, table rendering) can balloon a single row to megabytes, bloating the table and slowing command-logger:show.-v off in cron definitions where output capture is on.make:test).capture_output only where the output is genuinely diagnostic.--threshold flag in command-logger:purge ignores the config value.enabled: true in config.app:deploy vs. APP:DEPLOY).#[AsCommand] is supported natively since v1.6.0+; legacy setName() still works but may need updates for full compatibility.CommandLog entity to add metadata (e.g., userId, environment):
// src/Entity/CommandLog.php
#[ORM\Entity]
class CommandLog extends BaseCommandLog {
#[ORM\Column(type: 'string', nullable: true)]
private ?string $environment = null;
}
CommandLogger service to inject custom logic:
# config/services.yaml
Ayaou\CommandLoggerBundle\Service\CommandLogger:
arguments:
$environment: '%kernel.environment%'
How can I help you explore Laravel packages today?