Generates PSR-3 Logger implementation for PHP 8.5. Creates LoggerInterface implementations with log levels, context interpolation, and LoggerAwareTrait usage. Includes unit tests.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add majiayu000/claude-skill-registry --skill acc-create-psr3-logger --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Acc Create Psr3 Logger?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/majiayu000-acc-create-psr3-logger-claude-skill-registry)More formats (shields.io, HTML) on the badges page.
---
name: acc-create-psr3-logger
description: Generates PSR-3 Logger implementation for PHP 8.5. Creates LoggerInterface implementations with log levels, context interpolation, and LoggerAwareTrait usage. Includes unit tests.
---
# 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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
```json
{
"require": {
"psr/log": "^3.0"
}
}
```
## See Also
- `references/templates.md` - Additional logger implementations
- `references/examples.md` - Real-world usage examples
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!