-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Logger #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Logger #10
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Config; | ||
|
||
use Tempest\Log\LogConfig; | ||
|
||
return new LogConfig(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Discovery; | ||
|
||
use ReflectionClass; | ||
use Tempest\Container\Container; | ||
use Tempest\Log\Channels\LogChannel; | ||
use Tempest\Log\LogConfig; | ||
|
||
final readonly class LogHandlerDiscovery implements Discovery | ||
{ | ||
private const string CACHE_PATH = __DIR__ . '/log-handler-discovery.cache.php'; | ||
|
||
public function __construct( | ||
private LogConfig $logConfig, | ||
) { | ||
} | ||
|
||
public function discover(ReflectionClass $class): void | ||
{ | ||
if ( | ||
! $class->isInstantiable() | ||
|| ! $class->implementsInterface(LogChannel::class) | ||
) { | ||
return; | ||
} | ||
|
||
$this->logConfig->channels[$class->getName()] = $class->getName(); | ||
} | ||
|
||
public function hasCache(): bool | ||
{ | ||
return file_exists(self::CACHE_PATH); | ||
} | ||
|
||
public function storeCache(): void | ||
{ | ||
file_put_contents(self::CACHE_PATH, serialize($this->logConfig->channels)); | ||
} | ||
|
||
public function restoreCache(Container $container): void | ||
{ | ||
$channels = unserialize(file_get_contents(self::CACHE_PATH)); | ||
|
||
$this->logConfig->channels = $channels; | ||
} | ||
|
||
public function destroyCache(): void | ||
{ | ||
@unlink(self::CACHE_PATH); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log\Channels; | ||
|
||
use Monolog\Handler\HandlerInterface; | ||
use Monolog\Handler\StreamHandler; | ||
use Monolog\Level; | ||
use Monolog\Processor\PsrLogMessageProcessor; | ||
|
||
final class AppendLogChannel implements LogChannel | ||
{ | ||
public function handler(Level $level, array $config): HandlerInterface | ||
{ | ||
return new StreamHandler( | ||
stream: $config['path'] ?? 'logs/tempest.log', | ||
level: $level, | ||
bubble: $config['bubble'] ?? true, | ||
filePermission: $config['file_permission'] ?? null, | ||
useLocking: $config['use_locking'] ?? false | ||
); | ||
} | ||
|
||
public function processor(array $config): array | ||
{ | ||
return [ | ||
new PsrLogMessageProcessor(), | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log\Channels; | ||
|
||
use Monolog\Handler\HandlerInterface; | ||
use Monolog\Handler\RotatingFileHandler; | ||
use Monolog\Level; | ||
use Monolog\Processor\PsrLogMessageProcessor; | ||
|
||
final class DailyLogChannel implements LogChannel | ||
{ | ||
public function handler(Level $level, array $config): HandlerInterface | ||
{ | ||
return new RotatingFileHandler( | ||
filename: $config['path'] ?? 'logs/tempest.log', | ||
maxFiles: $config['rotation'] ?? 30, | ||
level: $level, | ||
bubble: $config['bubble'] ?? true, | ||
filePermission: $config['file_permission'] ?? null, | ||
useLocking: $config['use_locking'] ?? false | ||
); | ||
} | ||
|
||
public function processor(array $config): array | ||
{ | ||
return [ | ||
new PsrLogMessageProcessor(), | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log\Channels; | ||
|
||
use Monolog\Handler\HandlerInterface; | ||
use Monolog\Level; | ||
use Monolog\Processor\ProcessorInterface; | ||
|
||
interface LogChannel | ||
{ | ||
/** | ||
* @param Level $level | ||
* @param array $config | ||
* | ||
* @return array<int, HandlerInterface>|HandlerInterface | ||
*/ | ||
public function handler(Level $level, array $config): array|HandlerInterface; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be better to call this method |
||
|
||
/** | ||
* @return ProcessorInterface|array<int, ProcessorInterface> | ||
*/ | ||
public function processor(array $config): array|ProcessorInterface; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log; | ||
|
||
use Monolog\Level; | ||
use Monolog\Logger as Monolog; | ||
use Psr\Log\LoggerInterface; | ||
use Stringable; | ||
use Tempest\Container\Container; | ||
use Tempest\Log\Channels\LogChannel; | ||
use Tempest\Support\ArrayHelper; | ||
|
||
final class GenericLogger implements LoggerInterface | ||
{ | ||
public function __construct( | ||
private LogConfig $logConfig, | ||
private Container $container, | ||
/** @var array<class-string, Monolog> $drivers */ | ||
private array $drivers = [], | ||
) { | ||
|
||
} | ||
|
||
public function emergency(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Emergency, $message, $context); | ||
} | ||
|
||
public function alert(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Alert, $message, $context); | ||
} | ||
|
||
public function critical(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Critical, $message, $context); | ||
} | ||
|
||
public function error(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Error, $message, $context); | ||
} | ||
|
||
public function warning(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Warning, $message, $context); | ||
} | ||
|
||
public function notice(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Notice, $message, $context); | ||
} | ||
|
||
public function info(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Info, $message, $context); | ||
} | ||
|
||
public function debug(Stringable|string $message, array $context = []): void | ||
{ | ||
$this->writeLog(Level::Debug, $message, $context); | ||
} | ||
|
||
public function log($level, Stringable|string $message, array $context = []): void | ||
{ | ||
$level = Level::tryFrom($level) ?? Level::Info; | ||
|
||
$this->writeLog($level, $message, $context); | ||
} | ||
|
||
private function writeLog(Level $level, string $message, array $context): void | ||
{ | ||
$this->resolveDriver($this->logConfig->channel, $level)->log($level, $message, $context); | ||
} | ||
|
||
private function resolveDriver(string $channelName, Level $level): Monolog | ||
{ | ||
if (isset($this->drivers[$channelName])) { | ||
return $this->drivers[$channelName]; | ||
} | ||
|
||
/** @var LogChannel $channel */ | ||
$channel = $this->container->get($channelName); | ||
|
||
$config = $this->logConfig->channelsConfig[$channelName] ?? []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This config should be an object with typed properties, not an array |
||
|
||
return $this->drivers[$channelName] = new Monolog( | ||
name: $this->logConfig->prefix, | ||
handlers: ArrayHelper::wrap($channel->handler($level, $config)), | ||
processors: ArrayHelper::wrap($channel->processor($config)), | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log; | ||
|
||
use Tempest\Log\Channels\AppendLogChannel; | ||
use Tempest\Log\Channels\LogChannel; | ||
|
||
final class LogConfig | ||
{ | ||
public function __construct( | ||
/** @var LogChannel[] */ | ||
public array $channels = [], | ||
/** @var array<class-string<LogChannel>, array|string> */ | ||
public array $channelsConfig = [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we need to improve channel config |
||
/** @var class-string<LogChannel> */ | ||
public string $channel = AppendLogChannel::class, | ||
public string $prefix = 'tempest', | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log; | ||
|
||
use Psr\Log\LoggerInterface; | ||
|
||
interface Logger extends LoggerInterface | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tempest\Log; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use Tempest\Container\Container; | ||
use Tempest\Container\Initializer; | ||
use Tempest\Container\Singleton; | ||
|
||
#[Singleton] | ||
final readonly class LoggerInitializer implements Initializer | ||
{ | ||
public function initialize(Container $container): LoggerInterface | ||
{ | ||
return new GenericLogger( | ||
$container->get(LogConfig::class), | ||
$container, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
logs/ |
Uh oh!
There was an error while loading. Please reload this page.