Skip to content

Commit

Permalink
New default implementation of logger shared by default between master…
Browse files Browse the repository at this point in the history
… and child processes
  • Loading branch information
luzrain committed Oct 17, 2024
1 parent 6995e27 commit 55af621
Show file tree
Hide file tree
Showing 15 changed files with 143 additions and 270 deletions.
14 changes: 9 additions & 5 deletions src/Internal/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;

/**
* @internal
Expand All @@ -30,32 +31,35 @@ final class ErrorHandler
\E_CORE_ERROR => ['Core Error', LogLevel::CRITICAL],
];

private static LoggerInterface|null $logger = null;
private static bool $registered = false;
private static LoggerInterface $logger;

private function __construct()
{
}

public static function register(LoggerInterface $logger): void
{
if (self::$logger !== null) {
if (self::$registered === true) {
throw new \LogicException(\sprintf('%s(): Already registered', __METHOD__));
}

self::$registered = true;
self::$logger = $logger;
\set_error_handler(self::handleError(...));
\set_exception_handler(self::handleException(...));
}

public static function unregister(): void
{
if (self::$logger === null) {
if (self::$registered === false) {
return;
}

\restore_error_handler();
\restore_exception_handler();
self::$logger = null;
self::$registered = false;
self::$logger = new NullLogger();
}

/**
Expand All @@ -78,7 +82,7 @@ private static function handleError(int $type, string $message, string $file, in

public static function handleException(\Throwable $exception): void
{
if (self::$logger === null) {
if (self::$registered === false) {
throw new \LogicException(\sprintf('%s(): ErrorHandler is unregistered', __METHOD__), 0, $exception);
}

Expand Down
37 changes: 0 additions & 37 deletions src/Internal/Logger/ConsoleFormatter.php

This file was deleted.

66 changes: 43 additions & 23 deletions src/Internal/Logger/ConsoleLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

namespace Luzrain\PHPStreamServer\Internal\Logger;

use Amp\ByteStream\ResourceStream;
use Amp\ByteStream\WritableStream;
use Luzrain\PHPStreamServer\Internal\Console\Colorizer;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use function Amp\ByteStream\getStderr;

/**
* @internal
Expand All @@ -17,35 +15,57 @@ final class ConsoleLogger implements LoggerInterface
{
use LoggerTrait;

private readonly WritableStream $stream;
private readonly FormatterInterface $formatter;
private bool $colorSupport = false;
public const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_FORCE_OBJECT;

public function __construct(WritableStream $stream)
private const LEVELS_COLOR_MAP = [
'debug' => 'fg=15',
'info' => 'fg=116',
'notice' => 'fg=38',
'warning' => 'fg=yellow',
'error' => 'fg=red',
'critical' => 'fg=red',
'alert' => 'fg=red',
'emergency' => 'bg=red',
];

private ContextNormalizer $contextNormalizer;
private bool $colorSupport;
private string $channel = 'server';

public function __construct()
{
$this->contextNormalizer = new ContextNormalizer();
$this->colorSupport = Colorizer::hasColorSupport(getStderr()->getResource());
}

public function withChannel(string $channel): self
{
$this->stream = $stream;
$this->formatter = new ConsoleFormatter();
$that = clone $this;
$that->channel = $channel;

if ($stream instanceof ResourceStream) {
$this->colorSupport = Colorizer::hasColorSupport($stream->getResource());
}
return $that;
}

public function log(mixed $level, string|\Stringable $message, array $context = []): void
{
$this->doLog(new LogEntry(
time: new \DateTimeImmutable(),
level: $level,
channel: 'app',
message: (string) $message,
context: $context,
$time = (new \DateTimeImmutable('now'))->format(\DateTimeInterface::RFC3339);
$level = (string) $level;
$message = (string) $message;
$context = $this->contextNormalizer->normalize($context);
$context = $context === [] ? '' : \json_encode($this->contextNormalizer->normalize($context), self::DEFAULT_JSON_FLAGS);

$message = \rtrim(\sprintf(
"[%s] <color;fg=green>%s</>.<color;%s>%s</> %s %s",
$time,
$this->channel,
self::LEVELS_COLOR_MAP[\strtolower($level)] ?? 'fg=gray',
\strtoupper($level),
$message,
$context,
));
}

private function doLog(LogEntry $logEntry): void
{
$message = $this->formatter->format($logEntry);
$message = $this->colorSupport ? Colorizer::colorize($message) : Colorizer::stripTags($message);
$this->stream->write($message . PHP_EOL);

getStderr()->write($message . PHP_EOL);
}
}
80 changes: 48 additions & 32 deletions src/Internal/Logger/ContextNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,74 @@
*/
final class ContextNormalizer
{
private function __construct()
public function __construct()
{
}

/**
* @param array<string, mixed> $context
* @return array<string, string>
*/
public static function normalize(array $context): array
public function normalize(mixed $data): mixed
{
foreach ($context as $key => $val) {
$context[$key] = match(true) {
\is_array($val) => self::normalize($val),
$val instanceof \Throwable => self::formatException($val),
$val instanceof \DateTimeInterface => $val->format(\DateTimeInterface::RFC3339),
$val instanceof \JsonSerializable => \json_decode($val->jsonSerialize()),
$val instanceof \Stringable => (string) $val,
\is_scalar($val) || \is_null($val) => $val,
\is_object($val) => '[object ' . $val::class . ']',
default => '[' . \get_debug_type($val) . ']',
};
if (\is_array($data)) {
foreach ($data as $key => $value) {
$data[$key] = $this->normalize($value);
}

return $data;
}

if (\is_null($data) || \is_scalar($data)) {
return $data;
}

if ($data instanceof \Throwable) {
return $this->normalizeException($data);
}

if ($data instanceof \JsonSerializable) {
return $data->jsonSerialize();
}

if ($data instanceof \Stringable) {
return $data->__toString();
}

if ($data instanceof \DateTimeInterface) {
return $data->format(\DateTimeInterface::RFC3339);
}

if (\is_object($data)) {
return \sprintf('[object(%s)]', $data::class);
}

return $context;
if (\is_resource($data)) {
return \sprintf('[resource(%s)]', \get_resource_type($data));
}

return \sprintf('[unknown(%s)]', \get_debug_type($data));
}

/**
* @param array<string, string> $context
*/
public static function contextreplacement(string $message, array $context): string
private function normalizeException(\Throwable $e): string
{
if (\str_contains($message, '{')) {
$replacements = [];
foreach ($context as $key => $val) {
$replacements["{{$key}}"] = \is_array($val) ? '[array]' : (string) $val;
}
$message = \strtr($message, $replacements);
$str = $this->formatException($e, 'object');

if (null !== $previous = $e->getPrevious()) {
do {
$str .= "\n" . $this->formatException($previous, 'previous');
} while (null !== $previous = $previous->getPrevious());
}

return $message;
return $str;
}

private static function formatException(\Throwable $e): string
private function formatException(\Throwable $e, string $objectTitle): string
{
return \sprintf(
"[object] (%s(code:%d): %s at %s:%d)\n[stacktrace]\n%s",
"[%s(%s) code:%d]: %s at %s:%d",
$objectTitle,
$e::class,
$e->getCode(),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString(),
);
}
}
10 changes: 0 additions & 10 deletions src/Internal/Logger/FormatterInterface.php

This file was deleted.

33 changes: 0 additions & 33 deletions src/Internal/Logger/JsonFormatter.php

This file was deleted.

10 changes: 10 additions & 0 deletions src/Internal/Logger/LoggerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\Internal\Logger;

interface LoggerInterface extends \Psr\Log\LoggerInterface
{
public function withChannel(string $channel): self;
}
7 changes: 6 additions & 1 deletion src/Internal/Logger/NullLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Luzrain\PHPStreamServer\Internal\Logger;

use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;

/**
Expand All @@ -18,7 +17,13 @@ public function __construct()
{
}

public function withChannel(string $channel): self
{
return $this;
}

public function log(mixed $level, string|\Stringable $message, array $context = []): void
{
// do nothing
}
}
26 changes: 0 additions & 26 deletions src/Internal/Logger/TextFormatter.php

This file was deleted.

Loading

0 comments on commit 55af621

Please sign in to comment.