Added a remote logging handler to allow user to forward logs to remote target.
This commit is contained in:
@@ -194,6 +194,12 @@ return (function () {
|
||||
'level' => env('WS_LOGGER_SYSLOG_LEVEL', Level::Error),
|
||||
'name' => ag($config, 'name'),
|
||||
],
|
||||
'remote' => [
|
||||
'type' => 'remote',
|
||||
'enabled' => (bool)env('WS_LOGGER_REMOTE_ENABLE', false),
|
||||
'level' => env('WS_LOGGER_REMOTE_LEVEL', Level::Error),
|
||||
'url' => env('WS_LOGGER_REMOTE_URL', null),
|
||||
],
|
||||
];
|
||||
|
||||
$config['supported'] = [
|
||||
|
||||
@@ -203,6 +203,32 @@ return (function () {
|
||||
return $value;
|
||||
},
|
||||
],
|
||||
[
|
||||
'key' => 'WS_LOGGER_REMOTE_ENABLE',
|
||||
'description' => 'Enable logging to remote logger.',
|
||||
'type' => 'bool',
|
||||
],
|
||||
[
|
||||
'key' => 'WS_LOGGER_REMOTE_LEVEL',
|
||||
'description' => 'Set the log level for the remote logger. Default: ERROR.',
|
||||
'type' => 'string',
|
||||
],
|
||||
[
|
||||
'key' => 'WS_LOGGER_REMOTE_URL',
|
||||
'description' => 'The URL to the remote logger.',
|
||||
'type' => 'string',
|
||||
'validate' => function (mixed $value): string {
|
||||
if (!is_numeric($value) && empty($value)) {
|
||||
throw new ValidationException('Invalid remote logger URL. Empty value.');
|
||||
}
|
||||
|
||||
if (false === isValidURL($value)) {
|
||||
throw new ValidationException('Invalid remote logger URL. Must be a valid URL.');
|
||||
}
|
||||
return $value;
|
||||
},
|
||||
'mask' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$validateCronExpression = function (string $value): string {
|
||||
|
||||
20
src/API/System/Explode.php
Normal file
20
src/API/System/Explode.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\System;
|
||||
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
use App\Libs\Exceptions\RuntimeException;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
|
||||
final readonly class Explode
|
||||
{
|
||||
public const string URL = '%{api.prefix}/system/explode';
|
||||
|
||||
#[Get(self::URL . '[/]', name: 'system.explode')]
|
||||
public function __invoke(): iResponse
|
||||
{
|
||||
throw new RuntimeException('Throwing an exception to test exception handling.');
|
||||
}
|
||||
}
|
||||
75
src/Libs/Extends/RemoteHandler.php
Normal file
75
src/Libs/Extends/RemoteHandler.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Libs\Extends;
|
||||
|
||||
use App\Libs\Enums\Http\Method;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
final class RemoteHandler extends AbstractProcessingHandler
|
||||
{
|
||||
/**
|
||||
* @var array<ResponseInterface>
|
||||
*/
|
||||
private array $requests = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly iHttp $client,
|
||||
private readonly string $url,
|
||||
$level = Level::Debug,
|
||||
bool $bubble = true
|
||||
) {
|
||||
$this->bubble = $bubble;
|
||||
|
||||
parent::__construct($level, $bubble);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (count($this->requests) > 0) {
|
||||
foreach ($this->requests as $request) {
|
||||
try {
|
||||
$request->getStatusCode();
|
||||
} catch (Throwable $e) {
|
||||
syslog(LOG_DEBUG, self::class . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::__destruct();
|
||||
}
|
||||
|
||||
protected function write(LogRecord $record): void
|
||||
{
|
||||
$server = $_SERVER ?? [];
|
||||
|
||||
foreach ($server as $key => $value) {
|
||||
if (is_string($key) && str_starts_with(strtoupper($key), 'WS_')) {
|
||||
$server[$key] = '***';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->requests[] = $this->client->request(Method::POST->value, $this->url, [
|
||||
'timeout' => 6,
|
||||
'json' => [
|
||||
'id' => generateUUID(),
|
||||
'message' => $record->message,
|
||||
'trace' => ag($record->context, 'trace', []),
|
||||
'structured' => ag($record->context, 'structured', []),
|
||||
'server' => ag($_SERVER ?? [], ['HTTP_HOST', 'SERVER_NAME'], 'watchstate.cli'),
|
||||
'context' => $server,
|
||||
'raw' => $record->toArray(),
|
||||
]
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
syslog(LOG_ERR, sprintf('%s: %s. (%s:%d)', $e::class, $e->getMessage(), $e->getFile(), $e->getLine()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Libs\Exceptions\Backends\RuntimeException;
|
||||
use App\Libs\Exceptions\HttpException;
|
||||
use App\Libs\Extends\ConsoleHandler;
|
||||
use App\Libs\Extends\ConsoleOutput;
|
||||
use App\Libs\Extends\RemoteHandler;
|
||||
use App\Libs\Extends\RouterStrategy;
|
||||
use Closure;
|
||||
use ErrorException;
|
||||
@@ -29,6 +30,7 @@ use Psr\Log\LoggerInterface as iLogger;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -543,6 +545,20 @@ final class Initializer
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'remote':
|
||||
if (null !== ($remoteUrl = ag($context, 'url'))) {
|
||||
$logger->pushHandler(
|
||||
$wrap->withHandler(
|
||||
new RemoteHandler(
|
||||
Container::get(iHttp::class),
|
||||
$remoteUrl,
|
||||
ag($context, 'level', Level::Warning),
|
||||
(bool)ag($context, 'bubble', true),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'console':
|
||||
$logger->pushHandler($wrap->withHandler(new ConsoleHandler($this->cliOutput)));
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user