Claude-skill-registry acc-create-psr3-logger
Generates PSR-3 Logger implementation for PHP 8.5. Creates LoggerInterface implementations with log levels, context interpolation, and LoggerAwareTrait usage. Includes unit tests.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/acc-create-psr3-logger" ~/.claude/skills/majiayu000-claude-skill-registry-acc-create-psr3-logger && rm -rf "$T"
manifest:
skills/data/acc-create-psr3-logger/SKILL.mdsource content
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 | |
| LoggerAware Trait | For classes needing logger | |
| Null Logger | Testing/no-op logger | |
| Unit Tests | PHPUnit tests | |
File Naming
| Type | Pattern | Example |
|---|---|---|
| File Logger | | |
| Stream Logger | | |
| Null Logger | | |
| Trait | | |
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
- Additional logger implementationsreferences/templates.md
- Real-world usage examplesreferences/examples.md