create-psr3-logger
PSR-3 Logger Generator
Overview
Generates PSR-3 compliant logger implementations following Psr\Log\LoggerInterface.
When to Use
- Implementing custom logging solutions
- Creating application-specific loggers
- Building logging adapters for external services
- Testing with mock/null loggers
Generated Components
| Component | Description | Location |
|---|---|---|
| Logger Implementation | Concrete logger class | src/Infrastructure/Logger/ |
| LoggerAware Trait | For classes needing logger | src/Infrastructure/Logger/ |
| Null Logger | Testing/no-op logger | src/Infrastructure/Logger/ |
| Unit Tests | PHPUnit tests | tests/Unit/Infrastructure/Logger/ |
File Naming
| Type | Pattern | Example |
|---|---|---|
| File Logger | {Name}Logger.php |
FileLogger.php |
| Stream Logger | StreamLogger.php |
StreamLogger.php |
| Null Logger | NullLogger.php |
NullLogger.php |
| Trait | LoggerAwareTrait.php |
LoggerAwareTrait.php |
Template: File Logger
<?php
declare(strict_types=1);
namespace App\Infrastructure\Logger;
use DateTimeImmutable;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Stringable;
final class FileLogger implements LoggerInterface
{
private const DATE_FORMAT = 'Y-m-d H:i:s.u';
public function __construct(
private readonly string $logFile,
private readonly string $minLevel = LogLevel::DEBUG,
) {
}
public function emergency(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
public function alert(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::ALERT, $message, $context);
}
public function critical(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
public function error(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::ERROR, $message, $context);
}
public function warning(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::WARNING, $message, $context);
}
public function notice(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::NOTICE, $message, $context);
}
public function info(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::INFO, $message, $context);
}
public function debug(string|Stringable $message, array $context = []): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
public function log(mixed $level, string|Stringable $message, array $context = []): void
{
if (!$this->shouldLog($level)) {
return;
}
$timestamp = (new DateTimeImmutable())->format(self::DATE_FORMAT);
$interpolated = $this->interpolate((string) $message, $context);
$entry = sprintf(
"[%s] %s: %s%s\n",
$timestamp,
strtoupper($level),
$interpolated,
$this->formatContext($context),
);
file_put_contents($this->logFile, $entry, FILE_APPEND | LOCK_EX);
}
private function shouldLog(string $level): bool
{
$levels = [
LogLevel::DEBUG => 0,
LogLevel::INFO => 1,
LogLevel::NOTICE => 2,
LogLevel::WARNING => 3,
LogLevel::ERROR => 4,
LogLevel::CRITICAL => 5,
LogLevel::ALERT => 6,
LogLevel::EMERGENCY => 7,
];
return ($levels[$level] ?? 0) >= ($levels[$this->minLevel] ?? 0);
}
private function interpolate(string $message, array $context): string
{
$replace = [];
foreach ($context as $key => $value) {
if (is_string($value) || $value instanceof Stringable) {
$replace['{' . $key . '}'] = (string) $value;
}
}
return strtr($message, $replace);
}
private function formatContext(array $context): string
{
if (empty($context)) {
return '';
}
$filtered = array_filter(
$context,
fn($v) => !is_string($v) && !$v instanceof Stringable,
);
if (empty($filtered)) {
return '';
}
return ' ' . json_encode($filtered, JSON_UNESCAPED_SLASHES);
}
}
Template: Null Logger
<?php
declare(strict_types=1);
namespace App\Infrastructure\Logger;
use Psr\Log\LoggerInterface;
use Stringable;
final readonly class NullLogger implements LoggerInterface
{
public function emergency(string|Stringable $message, array $context = []): void
{
}
public function alert(string|Stringable $message, array $context = []): void
{
}
public function critical(string|Stringable $message, array $context = []): void
{
}
public function error(string|Stringable $message, array $context = []): void
{
}
public function warning(string|Stringable $message, array $context = []): void
{
}
public function notice(string|Stringable $message, array $context = []): void
{
}
public function info(string|Stringable $message, array $context = []): void
{
}
public function debug(string|Stringable $message, array $context = []): void
{
}
public function log(mixed $level, string|Stringable $message, array $context = []): void
{
}
}
Template: Logger Aware
<?php
declare(strict_types=1);
namespace App\Infrastructure\Logger;
use Psr\Log\LoggerInterface;
trait LoggerAwareTrait
{
private ?LoggerInterface $logger = null;
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
protected function getLogger(): LoggerInterface
{
return $this->logger ?? new NullLogger();
}
}
Template: Unit Test
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Infrastructure\Logger;
use App\Infrastructure\Logger\FileLogger;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
#[Group('unit')]
#[CoversClass(FileLogger::class)]
final class FileLoggerTest extends TestCase
{
private string $logFile;
protected function setUp(): void
{
$this->logFile = sys_get_temp_dir() . '/test_' . uniqid() . '.log';
}
protected function tearDown(): void
{
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
#[Test]
public function it_logs_message_with_level(): void
{
$logger = new FileLogger($this->logFile);
$logger->error('Test error message');
$content = file_get_contents($this->logFile);
self::assertStringContainsString('ERROR', $content);
self::assertStringContainsString('Test error message', $content);
}
#[Test]
public function it_interpolates_context_placeholders(): void
{
$logger = new FileLogger($this->logFile);
$logger->info('User {username} logged in', ['username' => 'john']);
$content = file_get_contents($this->logFile);
self::assertStringContainsString('User john logged in', $content);
}
#[Test]
public function it_respects_minimum_log_level(): void
{
$logger = new FileLogger($this->logFile, LogLevel::ERROR);
$logger->debug('Debug message');
$logger->info('Info message');
$logger->error('Error message');
$content = file_get_contents($this->logFile);
self::assertStringNotContainsString('Debug message', $content);
self::assertStringNotContainsString('Info message', $content);
self::assertStringContainsString('Error message', $content);
}
#[Test]
public function it_includes_context_in_log(): void
{
$logger = new FileLogger($this->logFile);
$logger->error('Error occurred', ['code' => 500, 'trace' => 'stack']);
$content = file_get_contents($this->logFile);
self::assertStringContainsString('500', $content);
}
}
Usage Examples
Basic Logging
<?php
use App\Infrastructure\Logger\FileLogger;
use Psr\Log\LogLevel;
$logger = new FileLogger('/var/log/app.log', LogLevel::INFO);
$logger->info('Application started');
$logger->error('Something went wrong', ['exception' => $e->getMessage()]);
With Context Interpolation
<?php
$logger->info('User {user_id} performed {action}', [
'user_id' => 123,
'action' => 'login',
'ip' => '192.168.1.1',
]);
// Output: User 123 performed login {"ip":"192.168.1.1"}
Logger Aware Service
<?php
declare(strict_types=1);
namespace App\Application\User\Handler;
use App\Infrastructure\Logger\LoggerAwareTrait;
use Psr\Log\LoggerAwareInterface;
final class CreateUserHandler implements LoggerAwareInterface
{
use LoggerAwareTrait;
public function __invoke(CreateUserCommand $command): void
{
$this->getLogger()->info('Creating user', ['email' => $command->email]);
// ... create user
$this->getLogger()->info('User created successfully');
}
}
Requirements
{
"require": {
"psr/log": "^3.0"
}
}
See Also
references/templates.md- Additional logger implementationsreferences/examples.md- Real-world usage examples
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12