-
Notifications
You must be signed in to change notification settings - Fork 0
Logging
Razy provides a PSR-3 compatible logging system with two main components: a standalone Logger for simple file-based logging, and a LogManager for multi-channel logging with pluggable handlers. Both implement PSR-3 interfaces without requiring psr/log as a dependency.
use Razy\Logger;
// Simple file-based logger
$logger = new Logger('/var/log/myapp');
$logger->info('User logged in', ['user_id' => 42]);
$logger->error('Payment failed', ['order_id' => 'ORD-123', 'reason' => 'timeout']);
// Multi-channel logging
use Razy\Log\LogManager;
use Razy\Log\FileHandler;
use Razy\Log\StderrHandler;
$log = new LogManager();
$log->addHandler('file', new FileHandler('/var/log/myapp'));
$log->addHandler('stderr', new StderrHandler());
$log->info('Application started');The Logger class provides straightforward file-based logging with date-based filenames:
use Razy\Logger;
use Razy\Contract\Log\LogLevel;
$logger = new Logger(
logDirectory: '/var/log/myapp', // null = null-logger (no-op)
minLevel: LogLevel::DEBUG, // minimum level to record
filenamePattern: 'Y-m-d', // date() format for log filenames
bufferEnabled: false // hold logs in memory
);
// PSR-3 level methods
$logger->emergency('System is down');
$logger->alert('Database connection lost');
$logger->critical('Uncaught exception', ['exception' => $e]);
$logger->error('Failed to process order {order_id}', ['order_id' => 'ORD-123']);
$logger->warning('Deprecated function called');
$logger->notice('User preference updated');
$logger->info('Email sent to {email}', ['email' => 'user@example.com']);
$logger->debug('Query executed in {time}ms', ['time' => 42]);
// Generic log method
$logger->log(LogLevel::INFO, 'Custom message');
// Adjust minimum level at runtime
$logger->setMinLevel(LogLevel::WARNING);
$logger->debug('This will be skipped'); // below WARNING, not loggedFiles are named using the filenamePattern (default: Y-m-d), producing files like 2026-02-23.log:
[2026-02-23 14:30:00] INFO: User logged in {"user_id":42}
[2026-02-23 14:30:01] ERROR: Payment failed {"order_id":"ORD-123","reason":"timeout"}
When no directory is given, the Logger acts as a no-op (PSR-3 NullLogger equivalent):
$nullLogger = new Logger(null);
$nullLogger->info('This goes nowhere'); // silently ignoredThe LogManager supports named channels, each with its own stack of handlers. It enables routing different log types to different destinations:
use Razy\Log\LogManager;
use Razy\Log\{FileHandler, StderrHandler, NullHandler};
use Razy\Contract\Log\LogLevel;
$log = new LogManager(defaultChannel: 'app');
// Register handlers on channels
$log->addHandler('app', new FileHandler('/var/log/app'));
$log->addHandler('app', new StderrHandler(LogLevel::ERROR)); // also stderr for errors
$log->addHandler('audit', new FileHandler('/var/log/audit', LogLevel::INFO));
$log->addHandler('debug', new StderrHandler(LogLevel::DEBUG));
// Log to default channel
$log->info('User logged in', ['user_id' => 42]);
// Log to a specific channel
$log->channel('audit')->info('Password changed', ['user_id' => 42]);
// Broadcast to multiple channels simultaneously
$log->stack(['app', 'audit'])->warning('Suspicious login attempt');The channel() and stack() methods set a one-time override — it resets after each log() call:
$log->channel('audit')->info('Audit event 1'); // goes to 'audit'
$log->info('Normal event'); // goes to default 'app'$log->hasChannel('audit'); // true
$log->getChannelNames(); // ['app', 'audit', 'debug']
$log->getHandlers('app'); // [FileHandler, StderrHandler]
$log->getDefaultChannel(); // 'app'
$log->setDefaultChannel('debug');Handlers implement LogHandlerInterface and decide how/where each log entry is written.
Writes to date-based log files with thread-safe LOCK_EX:
use Razy\Log\FileHandler;
use Razy\Contract\Log\LogLevel;
$handler = new FileHandler(
directory: '/var/log/myapp',
minLevel: LogLevel::WARNING, // only WARNING and above
filenamePattern: 'Y-m-d' // files: 2026-02-23.log
);
// Check if handler will process a level
$handler->isHandling(LogLevel::ERROR); // true
$handler->isHandling(LogLevel::DEBUG); // false
// Adjust at runtime
$handler->setMinLevel(LogLevel::DEBUG);Writes to php://stderr → useful for containerized environments (Docker, Kubernetes):
use Razy\Log\StderrHandler;
use Razy\Contract\Log\LogLevel;
$handler = new StderrHandler(minLevel: LogLevel::ERROR);No-op handler — absorbs all logs. Useful for testing:
use Razy\Log\NullHandler;
$handler = new NullHandler();
$handler->isHandling(LogLevel::EMERGENCY); // always trueImplement LogHandlerInterface for custom destinations:
use Razy\Contract\Log\LogHandlerInterface;
use Razy\Contract\Log\LogLevel;
class SlackHandler implements LogHandlerInterface
{
public function __construct(
private readonly string $webhookUrl,
private string $minLevel = LogLevel::CRITICAL
) {}
public function handle(
string $level,
string $message,
array $context,
string $timestamp,
string $channel
): void {
// POST to Slack webhook
$payload = json_encode([
'text' => "[{$timestamp}] [{$channel}] {$level}: {$message}",
]);
// ... HTTP request
}
public function isHandling(string $level): bool
{
$priorities = [
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 ($priorities[$level] ?? 0) >= ($priorities[$this->minLevel] ?? 0);
}
}PSR-3 log levels in order of severity (lowest to highest):
| Level | Constant | Priority | Use Case |
| --- | --- | --- | --- |
| debug | LogLevel::DEBUG | 0 | Detailed debug information |
| info | LogLevel::INFO | 1 | Informational events |
| notice | LogLevel::NOTICE | 2 | Normal but significant events |
| warning | LogLevel::WARNING | 3 | Exceptional but non-error |
| error | LogLevel::ERROR | 4 | Runtime errors |
| critical | LogLevel::CRITICAL | 5 | Critical conditions |
| alert | LogLevel::ALERT | 6 | Requires immediate action |
| emergency | LogLevel::EMERGENCY | 7 | System is unusable |
PSR-3 {placeholder} interpolation is supported in log messages:
$logger->info('User {username} logged in from {ip}', [
'username' => 'john',
'ip' => '192.168.1.1',
]);
// Output: "User john logged in from 192.168.1.1"
// Exception context (PSR-3 convention)
$logger->error('Operation failed', [
'exception' => $exception,
'order_id' => 'ORD-123',
]);
// Exception stack trace is appendedBoth Logger and LogManager support in-memory buffering for programmatic access:
// Logger with buffer
$logger = new Logger('/var/log/app', bufferEnabled: true);
$logger->info('Event 1');
$logger->error('Event 2');
// Read buffer
$entries = $logger->getBuffer();
// [
// ['level' => 'info', 'message' => 'Event 1', 'context' => [], 'timestamp' => '...'],
// ['level' => 'error', 'message' => 'Event 2', 'context' => [], 'timestamp' => '...'],
// ]
// Clear buffer
$logger->clearBuffer();
// LogManager with buffer
$log = new LogManager(bufferEnabled: true);
$log->addHandler('app', new FileHandler('/var/log/app'));
$log->info('Buffered too');
$entries = $log->getBuffer();| Method | Signature | Description |
| --- | --- | --- |
| __construct | (?string $logDirectory, string $minLevel, string $filenamePattern, bool $bufferEnabled) | Create logger |
| log | (mixed $level, string\|Stringable $message, array $context = []): void | Log a message |
| emergency/alert/critical/error/warning/notice/info/debug | (string\|Stringable $message, array $context = []): void | PSR-3 level methods |
| getMinLevel | (): string | Current minimum level |
| setMinLevel | (string $level): static | Change minimum level |
| getLogDirectory | (): ?string | Log directory path |
| getBuffer | (): array | Buffered entries |
| clearBuffer | (): static | Clear buffer |
| Method | Signature | Description |
| --- | --- | --- |
| __construct | (string $defaultChannel, bool $bufferEnabled) | Create manager |
| addHandler | (string $channel, LogHandlerInterface $handler): static | Add handler to channel |
| getHandlers | (string $channel): array | Handlers for channel |
| channel | (string $channel): static | Set next-log channel |
| stack | (array $channels): static | Broadcast to channels |
| getDefaultChannel | (): string | Default channel name |
| setDefaultChannel | (string $channel): static | Change default |
| getChannelNames | (): array | All channel names |
| hasChannel | (string $channel): bool | Check channel exists |
| log | (mixed $level, string\|Stringable $message, array $context = []): void | Log a message |
| getBuffer | (): array | Buffered entries |
| clearBuffer | (): static | Clear buffer |
| Method | Signature | Description |
| --- | --- | --- |
| handle | (string $level, string $message, array $context, string $timestamp, string $channel): void | Process a log entry |
| isHandling | (string $level): bool | Can handle this level? |