Skip to content

Commit

Permalink
feat: Allow logging to multiple sinks
Browse files Browse the repository at this point in the history
  • Loading branch information
tienvx committed Oct 28, 2024
1 parent ef3957e commit aa39504
Show file tree
Hide file tree
Showing 25 changed files with 855 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/PhpPact/FFI/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,41 @@ public function interactionContents(int $interaction, int $part, string $content
return $result;
}

public function loggerInit(): void
{
$this->call('pactffi_logger_init');
}

public function loggerAttachSink(string $sinkSpecifier, int $levelFilter): int
{
$method = 'pactffi_logger_attach_sink';
$result = $this->call($method, $sinkSpecifier, $levelFilter);
if (!is_int($result)) {
throw new InvalidResultException(sprintf('Invalid result of "%s". Expected "integer", but got "%s"', $method, get_debug_type($result)));
}
return $result;
}

public function loggerApply(): int
{
$method = 'pactffi_logger_apply';
$result = $this->call($method);
if (!is_int($result)) {
throw new InvalidResultException(sprintf('Invalid result of "%s". Expected "integer", but got "%s"', $method, get_debug_type($result)));
}
return $result;
}

public function fetchLogBuffer(?string $logId = null): string
{
$method = 'pactffi_fetch_log_buffer';
$result = $this->call($method, $logId);
if (!is_string($result)) {
throw new InvalidResultException(sprintf('Invalid result of "%s". Expected "string", but got "%s"', $method, get_debug_type($result)));
}
return $result;
}

public function getInteractionPartRequest(): int
{
return $this->getEnum('InteractionPart_Request');
Expand Down Expand Up @@ -550,6 +585,36 @@ public function getPactSpecificationUnknown(): int
return $this->getEnum('PactSpecification_Unknown');
}

public function getLevelFilterTrace(): int
{
return $this->getEnum('LevelFilter_Trace');
}

public function getLevelFilterDebug(): int
{
return $this->getEnum('LevelFilter_Debug');
}

public function getLevelFilterInfo(): int
{
return $this->getEnum('LevelFilter_Info');
}

public function getLevelFilterWarn(): int
{
return $this->getEnum('LevelFilter_Warn');
}

public function getLevelFilterError(): int
{
return $this->getEnum('LevelFilter_Error');
}

public function getLevelFilterOff(): int
{
return $this->getEnum('LevelFilter_Off');
}

private function getEnum(string $name): int
{
$value = $this->get($name);
Expand Down
20 changes: 20 additions & 0 deletions src/PhpPact/FFI/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public function responseStatusV2(int $interaction, ?string $status): bool;

public function interactionContents(int $interaction, int $part, string $contentType, string $contents): int;

public function loggerInit(): void;

public function loggerAttachSink(string $sinkSpecifier, int $levelFilter): int;

public function loggerApply(): int;

public function fetchLogBuffer(?string $logId = null): string;

public function getInteractionPartRequest(): int;

public function getInteractionPartResponse(): int;
Expand All @@ -138,4 +146,16 @@ public function getPactSpecificationV3(): int;
public function getPactSpecificationV4(): int;

public function getPactSpecificationUnknown(): int;

public function getLevelFilterTrace(): int;

public function getLevelFilterDebug(): int;

public function getLevelFilterInfo(): int;

public function getLevelFilterWarn(): int;

public function getLevelFilterError(): int;

public function getLevelFilterOff(): int;
}
14 changes: 14 additions & 0 deletions src/PhpPact/Log/Enum/LogLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace PhpPact\Log\Enum;

enum LogLevel: string
{
case TRACE = 'TRACE';
case DEBUG = 'DEBUG';
case INFO = 'INFO';
case WARN = 'WARN';
case ERROR = 'ERROR';
case OFF = 'OFF';
case NONE = 'NONE';
}
9 changes: 9 additions & 0 deletions src/PhpPact/Log/Exception/LogException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace PhpPact\Log\Exception;

use PhpPact\Exception\BaseException;

class LogException extends BaseException
{
}
15 changes: 15 additions & 0 deletions src/PhpPact/Log/Exception/LoggerApplyException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace PhpPact\Log\Exception;

class LoggerApplyException extends LoggerException
{
public function __construct(int $code)
{
$message = match ($code) {
-1 => "Can't set logger (applying the logger failed, perhaps because one is applied already).",
default => 'Unknown error',
};
parent::__construct($message, $code);
}
}
20 changes: 20 additions & 0 deletions src/PhpPact/Log/Exception/LoggerAttachSinkException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace PhpPact\Log\Exception;

class LoggerAttachSinkException extends LoggerException
{
public function __construct(int $code)
{
$message = match ($code) {
-1 => "Can't set logger (applying the logger failed, perhaps because one is applied already).",
-2 => 'No logger has been initialized (call `pactffi_logger_init` before any other log function).',
-3 => 'The sink specifier was not UTF-8 encoded.',
-4 => 'The sink type specified is not a known type (known types: "stdout", "stderr", "buffer", or "file /some/path").',
-5 => 'No file path was specified in a file-type sink specification.',
-6 => 'Opening a sink to the specified file path failed (check permissions).',
default => 'Unknown error',
};
parent::__construct($message, $code);
}
}
7 changes: 7 additions & 0 deletions src/PhpPact/Log/Exception/LoggerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace PhpPact\Log\Exception;

class LoggerException extends LogException
{
}
7 changes: 7 additions & 0 deletions src/PhpPact/Log/Exception/LoggerUnserializeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace PhpPact\Log\Exception;

class LoggerUnserializeException extends LoggerException
{
}
92 changes: 92 additions & 0 deletions src/PhpPact/Log/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace PhpPact\Log;

use PhpPact\FFI\Client;
use PhpPact\FFI\ClientInterface;
use PhpPact\Log\Enum\LogLevel;
use PhpPact\Log\Exception\LoggerApplyException;
use PhpPact\Log\Exception\LoggerAttachSinkException;
use PhpPact\Log\Exception\LoggerUnserializeException;
use PhpPact\Log\Model\SinkInterface;

final class Logger implements LoggerInterface
{
private static ?self $instance = null;
/**
* @var SinkInterface[]
*/
private array $sinks = [];
private bool $applied = false;

protected function __construct(private ClientInterface $client)
{
$this->client->loggerInit();
}

protected function __clone(): void
{
}

public function __wakeup(): never
{
throw new LoggerUnserializeException('Cannot unserialize a singleton.');
}

public static function instance(?ClientInterface $client = null): Logger
{
if (!isset(self::$instance)) {
self::$instance = new self($client ?? new Client());
}
return self::$instance;
}

public static function tearDown(): void
{
self::$instance = null;
}

public function attach(SinkInterface $sink): void
{
if ($this->applied) {
return;
}
$this->sinks[] = $sink;
}

public function apply(): void
{
if ($this->applied) {
return;
}
foreach ($this->sinks as $sink) {
$error = $this->client->loggerAttachSink($sink->getSpecifier(), $this->getLevelFilter($sink->getLevel()));
if ($error) {
throw new LoggerAttachSinkException($error);
}
}
$error = $this->client->loggerApply();
if ($error) {
throw new LoggerApplyException($error);
}
$this->applied = true;
}

public function fetchBuffer(): string
{
return $this->client->fetchLogBuffer();
}

private function getLevelFilter(LogLevel $level): int
{
return match ($level) {
LogLevel::TRACE => $this->client->getLevelFilterTrace(),
LogLevel::DEBUG => $this->client->getLevelFilterDebug(),
LogLevel::INFO => $this->client->getLevelFilterInfo(),
LogLevel::WARN => $this->client->getLevelFilterWarn(),
LogLevel::ERROR => $this->client->getLevelFilterError(),
LogLevel::OFF => $this->client->getLevelFilterOff(),
LogLevel::NONE => $this->client->getLevelFilterOff(),
};
}
}
14 changes: 14 additions & 0 deletions src/PhpPact/Log/LoggerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace PhpPact\Log;

use PhpPact\Log\Model\SinkInterface;

interface LoggerInterface
{
public function attach(SinkInterface $sink): void;

public function apply(): void;

public function fetchBuffer(): string;
}
17 changes: 17 additions & 0 deletions src/PhpPact/Log/Model/AbstractSink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace PhpPact\Log\Model;

use PhpPact\Log\Enum\LogLevel;

abstract class AbstractSink implements SinkInterface
{
public function __construct(private LogLevel $level)
{
}

public function getLevel(): LogLevel
{
return $this->level;
}
}
11 changes: 11 additions & 0 deletions src/PhpPact/Log/Model/Buffer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpPact\Log\Model;

class Buffer extends AbstractSink
{
public function getSpecifier(): string
{
return 'buffer';
}
}
18 changes: 18 additions & 0 deletions src/PhpPact/Log/Model/File.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace PhpPact\Log\Model;

use PhpPact\Log\Enum\LogLevel;

class File extends AbstractSink
{
public function __construct(private string $path, LogLevel $level)
{
parent::__construct($level);
}

public function getSpecifier(): string
{
return "file {$this->path}";
}
}
12 changes: 12 additions & 0 deletions src/PhpPact/Log/Model/SinkInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace PhpPact\Log\Model;

use PhpPact\Log\Enum\LogLevel;

interface SinkInterface
{
public function getLevel(): LogLevel;

public function getSpecifier(): string;
}
11 changes: 11 additions & 0 deletions src/PhpPact/Log/Model/Stderr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpPact\Log\Model;

class Stderr extends AbstractSink
{
public function getSpecifier(): string
{
return 'stderr';
}
}
11 changes: 11 additions & 0 deletions src/PhpPact/Log/Model/Stdout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpPact\Log\Model;

class Stdout extends AbstractSink
{
public function getSpecifier(): string
{
return 'stdout';
}
}
Loading

0 comments on commit aa39504

Please sign in to comment.