Skip to content

Commit

Permalink
Added Gelf logger handler implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
luzrain committed Nov 8, 2024
1 parent e9423f0 commit 37660ca
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 38 deletions.
12 changes: 5 additions & 7 deletions src/BundledPlugin/Logger/Formatter/GelfFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@
private const VERSION = '1.1';

public const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_UNICODE
| JSON_PRESERVE_ZERO_FRACTION
| JSON_INVALID_UTF8_SUBSTITUTE
| JSON_PARTIAL_OUTPUT_ON_ERROR
| JSON_PRESERVE_ZERO_FRACTION
| JSON_INVALID_UTF8_SUBSTITUTE
| JSON_PARTIAL_OUTPUT_ON_ERROR
;

private string $hostName;

public function __construct(
string $hostName = null,
private bool $includeStacktraces = false,
) {
public function __construct(string $hostName = null, private bool $includeStacktraces = false)
{
$this->hostName = $hostName ?? \gethostname();
}

Expand Down
8 changes: 6 additions & 2 deletions src/BundledPlugin/Logger/Handler/ConsoleHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;

use Amp\ByteStream\WritableResourceStream;
use Amp\Future;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Formatter\ConsoleFormatter;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\FormatterInterface;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogEntry;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogLevel;
use Luzrain\PHPStreamServer\Internal\Console\Colorizer;
use function Amp\async;
use function Luzrain\PHPStreamServer\Internal\getStderr;
use function Luzrain\PHPStreamServer\Internal\getStdout;

Expand All @@ -31,16 +33,18 @@ public function __construct(
parent::__construct($level, $channels);
}

public function start(): void
public function start(): Future
{
$this->stream = $this->output === self::OUTPUT_STDERR ? getStderr() : getStdout();
$this->colorSupport = Colorizer::hasColorSupport($this->stream->getResource());

return async(static fn() => null);
}

public function handle(LogEntry $record): void
{
$message = $this->formatter->format($record);
$message = $this->colorSupport ? Colorizer::colorize($message) : Colorizer::stripTags($message);
$this->stream->write($message . PHP_EOL);
$this->stream->write($message . "\n");
}
}
30 changes: 17 additions & 13 deletions src/BundledPlugin/Logger/Handler/FileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

use Amp\ByteStream\ReadableResourceStream;
use Amp\ByteStream\WritableResourceStream;
use Amp\Future;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Formatter\StringFormatter;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\FormatterInterface;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogEntry;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogLevel;
use Revolt\EventLoop;
use function Amp\async;
use function Amp\delay;

final class FileHandler extends Handler
Expand Down Expand Up @@ -44,23 +46,25 @@ public function __construct(
parent::__construct($level, $channels);
}

public function start(): void
public function start(): Future
{
$file = !\str_starts_with($this->filename, '/') ? \getcwd() . '/' . $this->filename : $this->filename;
$this->logFile = new \SplFileInfo($file);
return async(function () {
$file = !\str_starts_with($this->filename, '/') ? \getcwd() . '/' . $this->filename : $this->filename;
$this->logFile = new \SplFileInfo($file);

if(!\is_dir($this->logFile->getPath())) {
\mkdir(directory: $this->logFile->getPath(), recursive: true);
}
if(!\is_dir($this->logFile->getPath())) {
\mkdir(directory: $this->logFile->getPath(), recursive: true);
}

$this->stream = new WritableResourceStream(\fopen($this->logFile->getPathname(), 'a'));
\chmod($this->logFile->getPathname(), $this->permission);
$this->stream = new WritableResourceStream(\fopen($this->logFile->getPathname(), 'a'));
\chmod($this->logFile->getPathname(), $this->permission);

if ($this->rotate) {
$this->scheduleRotate();
}
if ($this->rotate) {
$this->scheduleRotate();
}

$this->pause = false;
$this->pause = false;
});
}

private function scheduleRotate(): void
Expand Down Expand Up @@ -157,6 +161,6 @@ public function handle(LogEntry $record): void
delay(0.01);
}

$this->stream->write($this->formatter->format($record) . PHP_EOL);
$this->stream->write($this->formatter->format($record) . "\n");
}
}
64 changes: 62 additions & 2 deletions src/BundledPlugin/Logger/Handler/GelfHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,85 @@

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;

use Amp\Future;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Formatter\GelfFormatter;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\FormatterInterface;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport\GelfHttpTransport;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport\GelfTcpTransport;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport\GelfTransport;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport\GelfUdpTransport;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogEntry;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogLevel;
use Revolt\EventLoop;
use function Amp\async;

final class GelfHandler extends Handler
{
private FormatterInterface $formatter;
private GelfTransport $transport;

/**
* @param string $address gelf address. Can start with udp:// tcp://, http:// or https://
*/
public function __construct(
private readonly string $address,
string $address,
string|null $hostName = null,
bool $includeStacktraces = false,
LogLevel $level = LogLevel::DEBUG,
array $channels = [],
) {
[$scheme, $host, $port] = $this->parseAddress($address);
$this->formatter = new GelfFormatter($hostName, $includeStacktraces);
$this->transport = match ($scheme) {
'udp' => new GelfUdpTransport($host, $port),
'tcp' => new GelfTcpTransport($host, $port),
'http', 'https' => new GelfHttpTransport($address),
};
parent::__construct($level, $channels);
}

public function start(): void
/**
* @return array{0: string, 1: string, 2: int}
*/
private function parseAddress(string $address): array
{
if (
!\str_starts_with($address, 'udp://') &&
!\str_starts_with($address, 'tcp://') &&
!\str_starts_with($address, 'http://') &&
!\str_starts_with($address, 'https://')
) {
throw new \InvalidArgumentException('Address should start with "udp://", "tcp://", "http://" or "https://"');
}

$parts = \parse_url($address);
if ($parts === false || !isset($parts['scheme'], $parts['host'])) {
throw new \InvalidArgumentException('Invalid address format');
}

$scheme = $parts['scheme'];
$host = $parts['host'];
$port = $parts['port'] ?? match ($scheme) {
'http' => 80,
'https' => 443,
default => throw new \InvalidArgumentException('Address should contain port'),
};

return [$scheme, $host, $port];
}

public function start(): Future
{
return async(function () {
$this->transport->start();
});
}

public function handle(LogEntry $record): void
{
EventLoop::queue(function () use ($record) {
$this->transport->write($this->formatter->format($record));
});
}
}
8 changes: 6 additions & 2 deletions src/BundledPlugin/Logger/Handler/SyslogHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;

use Amp\Future;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Formatter\StringFormatter;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\FormatterInterface;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Handler;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogEntry;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogLevel;
use Luzrain\PHPStreamServer\Server;
use function Amp\async;

final class SyslogHandler extends Handler
{
Expand All @@ -20,18 +22,20 @@ final class SyslogHandler extends Handler
*/
public function __construct(
private readonly string $prefix = Server::SHORTNAME,
private readonly int $flags = LOG_PID,
private readonly int $flags = 0,
private readonly string|int $facility = LOG_USER,
LogLevel $level = LogLevel::DEBUG,
array $channels = [],
) {
parent::__construct($level, $channels);
}

public function start(): void
public function start(): Future
{
$this->formatter = new StringFormatter(messageFormat: '{channel}.{level} {message} {context}');
\openlog($this->prefix, $this->flags, $this->facility);

return async(static fn () => null);
}

public function handle(LogEntry $record): void
Expand Down
3 changes: 2 additions & 1 deletion src/BundledPlugin/Logger/HandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger;

use Amp\Future;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\LogEntry;

interface HandlerInterface
{
public function start(): void;
public function start(): Future;

public function isHandling(LogEntry $record): bool;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport;

use Amp\Http\Client\HttpClient;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use Amp\Http\Client\SocketException;

final class GelfHttpTransport implements GelfTransport
{
private HttpClient $httpClient;
private bool $inErrorState = false;

public function __construct(private readonly string $url)
{
if (!\class_exists(HttpClient::class)) {
throw new \RuntimeException(\sprintf('You cannot use "%s" as the "http-client" package is not installed. Try running "composer require amphp/http-client".', __CLASS__));
}
}

public function start(): void
{
$this->httpClient = (new HttpClientBuilder())->followRedirects(0)->build();
}

public function write(string $buffer): void
{
$request = new Request($this->url, 'POST', $buffer);
$request->setHeader('Content-Type', 'application/json');
$request->setTransferTimeout(5);

try {
$this->httpClient->request($request);
$this->inErrorState = false;
} catch (SocketException $e) {
if($this->inErrorState === false) {
\trigger_error($e->getMessage(), E_USER_WARNING);
$this->inErrorState = true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport;

use Amp\ByteStream\StreamException;
use Amp\ByteStream\WritableStream;
use Amp\Socket\ConnectContext;
use Amp\Socket\ConnectException;
use Amp\Socket\DnsSocketConnector;
use Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\NullWritableStream;
use Revolt\EventLoop;

final class GelfTcpTransport implements GelfTransport
{
private const CONNECT_TIMEOUT = 4;
private const RECONNECT_TIMEOUT = 10;

private WritableStream $socket;
private bool $inErrorState = false;

public function __construct(private readonly string $host, private readonly int $port)
{
}

public function start(): void
{
$connector = new DnsSocketConnector();
$context = (new ConnectContext())->withConnectTimeout(self::CONNECT_TIMEOUT);

try {
$this->socket = $connector->connect(\sprintf('tcp://%s:%d', $this->host, $this->port), $context);
$this->inErrorState = false;
} catch (ConnectException $e) {
$this->socket = new NullWritableStream();

if ($this->inErrorState === false) {
\trigger_error($e->getMessage(), E_USER_WARNING);
$this->inErrorState = true;
}

EventLoop::delay(self::RECONNECT_TIMEOUT, function () {
$this->start();
});
}
}

public function write(string $buffer): void
{
try {
$this->socket->write($buffer . "\0");
} catch (StreamException) {
$this->start();
// try to send second time after connect
try {
$this->socket->write($buffer. "\0");
} catch (StreamException) {
// do nothing
}
}
}
}
12 changes: 12 additions & 0 deletions src/BundledPlugin/Logger/Internal/GelfTransport/GelfTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\BundledPlugin\Logger\Internal\GelfTransport;

interface GelfTransport
{
public function start(): void;

public function write(string $buffer): void;
}
Loading

0 comments on commit 37660ca

Please sign in to comment.