Skip to content
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: Allow logging to multiple sinks #689

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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