refactor(logging)
This commit is contained in:
@@ -12,6 +12,7 @@ use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Apoloz\AlfaBank\Services\OpenSslCmsSigner;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class AlfaBank
|
||||
{
|
||||
@@ -43,7 +44,8 @@ class AlfaBank
|
||||
?string $clientSecret = null,
|
||||
?string $redirectUri = null,
|
||||
?string $signingCertificatePath = null,
|
||||
?string $signingPrivateKeyPath = null
|
||||
?string $signingPrivateKeyPath = null,
|
||||
?LoggerInterface $logger = null,
|
||||
): AlfaBankConnector {
|
||||
$config = new AlfaConfig(
|
||||
baseUrl: $environment->value,
|
||||
@@ -62,7 +64,13 @@ class AlfaBank
|
||||
|
||||
$signer = new OpenSslCmsSigner($signerCert, $signerKey);
|
||||
|
||||
return new AlfaBankConnector($config, $signer);
|
||||
$connector = new AlfaBankConnector($config, $signer);
|
||||
|
||||
if ($logger) {
|
||||
$connector->withLogging($logger);
|
||||
}
|
||||
|
||||
return $connector;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
@@ -75,7 +83,8 @@ class AlfaBank
|
||||
Environment $environment = Environment::SANDBOX,
|
||||
?string $caBundlePath = null,
|
||||
?string $signingCertificatePath = null,
|
||||
?string $signingPrivateKeyPath = null
|
||||
?string $signingPrivateKeyPath = null,
|
||||
?LoggerInterface $logger = null,
|
||||
): SbpConnector {
|
||||
$baseUrl = $environment === Environment::PRODUCTION
|
||||
? Environment::SBP_PRODUCTION->value
|
||||
@@ -90,7 +99,7 @@ class AlfaBank
|
||||
signingPrivateKeyPath: $signingPrivateKeyPath
|
||||
);
|
||||
|
||||
return new SbpConnector($config, $keyNameAlias);
|
||||
return new SbpConnector($config, $keyNameAlias, logger: $logger);
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use Apoloz\AlfaBank\Http\Middleware\VerifyJwsResponseMiddleware;
|
||||
use Apoloz\AlfaBank\Http\Pagination\AlfaOffsetPaginator;
|
||||
use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Saloon\Helpers\OAuth2\OAuthConfig;
|
||||
use Saloon\Http\Connector;
|
||||
use Saloon\Http\OAuth2\GetClientCredentialsTokenRequest;
|
||||
@@ -37,10 +38,13 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
|
||||
public function __construct(
|
||||
protected AlfaConfig $alfaConfig,
|
||||
protected ?SignerInterface $signer = null
|
||||
protected ?SignerInterface $signer = null,
|
||||
protected bool $loggingEnabled = true,
|
||||
protected ?LoggerInterface $logger = null,
|
||||
) {
|
||||
$this->tries = 3;
|
||||
$this->retryInterval = 500;
|
||||
$this->logger = $this->logger ?? new NullLogger;
|
||||
}
|
||||
|
||||
public function resolveBaseUrl(): string
|
||||
@@ -50,7 +54,12 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
|
||||
public function boot(PendingRequest $pendingRequest): void
|
||||
{
|
||||
//
|
||||
$pendingRequest->middleware()->onRequest(new RequestIdMiddleware);
|
||||
|
||||
if ($this->loggingEnabled) {
|
||||
$pendingRequest->middleware()->onRequest(new LoggingRequestMiddleware($this->logger));
|
||||
$pendingRequest->middleware()->onResponse(new LoggingResponseMiddleware($this->logger));
|
||||
}
|
||||
}
|
||||
|
||||
public function paginate(Request $request): OffsetPaginator
|
||||
@@ -101,12 +110,19 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="logging_mutator" intent="Allow attaching a PSR-3 logger to the connector for debugging.">
|
||||
// <BELIEF_STATE "Logging should be an optional, opt-in feature. This method provides a fluent interface to attach a logging middleware, which will automatically sanitize sensitive data before output.">
|
||||
// <SECTION name="logging_mutator" intent="Attach a PSR-3 logger to the connector for debugging.">
|
||||
// <BELIEF_STATE "Logging is enabled by default with a NullLogger (silent). Pass a real logger to start seeing output, or disable entirely via withoutLogging().">
|
||||
public function withLogging(LoggerInterface $logger): static
|
||||
{
|
||||
$this->middleware()->onRequest(new LoggingRequestMiddleware($logger));
|
||||
$this->middleware()->onResponse(new LoggingResponseMiddleware($logger));
|
||||
$this->logger = $logger;
|
||||
$this->loggingEnabled = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withoutLogging(): static
|
||||
{
|
||||
$this->loggingEnabled = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -159,12 +175,5 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
// OAuth токен запросы требуют application/x-www-form-urlencoded
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
new RequestIdMiddleware,
|
||||
];
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Saloon\Contracts\RequestMiddleware;
|
||||
use Saloon\Contracts\ResponseMiddleware;
|
||||
use Saloon\Http\PendingRequest;
|
||||
use Saloon\Http\Response;
|
||||
|
||||
class LoggingMiddleware implements RequestMiddleware, ResponseMiddleware
|
||||
{
|
||||
// <SECTION name="constructor_definition" intent="Initialize the middleware with a logger instance.">
|
||||
// <BELIEF_STATE "The constructor injects a `LoggerInterface` instance, making the logging functionality easily testable and configurable. This adheres to dependency inversion principles, allowing the middleware to log messages without being tightly coupled to a specific logging implementation.">
|
||||
public function __construct(protected LoggerInterface $logger) {}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="on_request_method" intent="Log outgoing API requests.">
|
||||
// <BELIEF_STATE "The `onRequest` method intercepts each outgoing HTTP request. It logs the request method and endpoint, along with masked headers and body content. Masking of sensitive information like authorization headers and payment data is performed by `maskHeaders` and `maskBody` methods, ensuring that debugging logs do not compromise security.">
|
||||
public function onRequest(PendingRequest $request): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Request: {$request->getMethod()->value} {$request->getRequest()->resolveEndpoint()}", [
|
||||
'headers' => $this->maskHeaders($request->headers()->all()),
|
||||
'body' => $this->maskBody($request->body()?->all()),
|
||||
]);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="on_response_method" intent="Log incoming API responses.">
|
||||
// <BELIEF_STATE "The `onResponse` method intercepts each incoming HTTP response. It logs the response status and the masked response body. This provides valuable information for debugging API integrations and understanding the server's responses, all while maintaining the privacy of potentially sensitive data within the response payload.">
|
||||
public function onResponse(Response $response): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Response: {$response->status()}", [
|
||||
'body' => $this->maskBody($response->json()),
|
||||
]);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="mask_headers_method" intent="Mask sensitive information in HTTP headers.">
|
||||
// <BELIEF_STATE "This protected helper method iterates through the request headers and replaces the `Authorization` header's value with a generic placeholder. This prevents sensitive tokens from being inadvertently exposed in logs, which is a crucial security measure.">
|
||||
protected function maskHeaders(array $headers): array
|
||||
{
|
||||
if (isset($headers['Authorization'])) {
|
||||
$headers['Authorization'] = 'Bearer *****';
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="mask_body_method" intent="Mask sensitive information in request/response bodies.">
|
||||
// <BELIEF_STATE "This protected recursive helper method inspects the request or response body for predefined sensitive keys (e.g., `pan`, `client_secret`). If a sensitive key is found, its value is replaced with a mask. The method recursively processes nested arrays, ensuring that sensitive data is masked even within complex JSON structures, thereby enhancing log security.">
|
||||
protected function maskBody(mixed $body): mixed
|
||||
{
|
||||
if (! is_array($body)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$sensitiveKeys = ['pan', 'cvc', 'digestSignatures', 'client_secret', 'refresh_token', 'access_token'];
|
||||
|
||||
foreach ($body as $key => $value) {
|
||||
if (in_array($key, $sensitiveKeys, true)) {
|
||||
$body[$key] = '*****';
|
||||
} elseif (is_array($value)) {
|
||||
$body[$key] = $this->maskBody($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
// </SECTION>
|
||||
@@ -14,10 +14,14 @@ class LoggingRequestMiddleware implements RequestMiddleware
|
||||
|
||||
public function __invoke(PendingRequest $request): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Request: {$request->getMethod()->value} {$request->getRequest()->resolveEndpoint()}", [
|
||||
'headers' => $this->maskHeaders($request->headers()->all()),
|
||||
'body' => $this->maskBody($request->body()?->all()),
|
||||
]);
|
||||
$this->logger->info(
|
||||
"AlfaBank Request: {$request->getMethod()->value} {$request->getUrl()}",
|
||||
[
|
||||
'host' => $request->getConnector()->resolveBaseUrl(),
|
||||
'headers' => $this->maskHeaders($request->headers()->all()),
|
||||
'body' => $this->maskBody($request->body()?->all()),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function maskHeaders(array $headers): array
|
||||
|
||||
@@ -14,9 +14,21 @@ class LoggingResponseMiddleware implements ResponseMiddleware
|
||||
|
||||
public function __invoke(Response $response): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Response: {$response->status()}", [
|
||||
'body' => $this->maskBody($response->json()),
|
||||
]);
|
||||
$pendingRequest = $response->getPendingRequest();
|
||||
|
||||
try {
|
||||
$body = $this->maskBody($response->json());
|
||||
} catch (\JsonException) {
|
||||
$body = $response->body();
|
||||
}
|
||||
|
||||
$this->logger->info(
|
||||
"AlfaBank Response: {$response->status()} {$pendingRequest->getUrl()}",
|
||||
[
|
||||
'host' => $pendingRequest->getConnector()->resolveBaseUrl(),
|
||||
'body' => $body,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function maskBody(mixed $body): mixed
|
||||
|
||||
@@ -24,6 +24,7 @@ use Apoloz\AlfaBank\Services\OpenSslCmsSigner;
|
||||
use Apoloz\AlfaBank\Services\PhpseclibCardEncryptor;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
@@ -68,10 +69,16 @@ class AlfaBankServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
$this->app->singleton(AlfaBankConnector::class, function ($app) {
|
||||
return new AlfaBankConnector(
|
||||
$loggingEnabled = $app['config']['alfa-bank.logging.enabled'] ?? true;
|
||||
|
||||
$connector = new AlfaBankConnector(
|
||||
alfaConfig: $app->make('alfa.baas.config'),
|
||||
signer: $app->make(SignerInterface::class)
|
||||
signer: $app->make(SignerInterface::class),
|
||||
loggingEnabled: $loggingEnabled,
|
||||
logger: $loggingEnabled ? $app->make(LoggerInterface::class) : null,
|
||||
);
|
||||
|
||||
return $connector;
|
||||
});
|
||||
$this->app->alias(AlfaBankConnector::class, 'alfa.baas');
|
||||
|
||||
@@ -90,10 +97,13 @@ class AlfaBankServiceProvider extends ServiceProvider
|
||||
|
||||
$this->app->singleton(SbpConnector::class, function ($app) {
|
||||
$config = $app['config']['alfa-bank.sbp'];
|
||||
$loggingEnabled = $app['config']['alfa-bank.logging.enabled'] ?? true;
|
||||
|
||||
return new SbpConnector(
|
||||
alfaConfig: $app->make('alfa.sbp.config'),
|
||||
keyNameAlias: $config['key_name_alias'] ?? null
|
||||
keyNameAlias: $config['key_name_alias'] ?? null,
|
||||
loggingEnabled: $loggingEnabled,
|
||||
logger: $loggingEnabled ? $app->make(LoggerInterface::class) : null,
|
||||
);
|
||||
});
|
||||
$this->app->alias(SbpConnector::class, 'alfa.sbp');
|
||||
|
||||
@@ -60,5 +60,18 @@ return [
|
||||
'encryption' => [
|
||||
'card_public_key' => env('ALFA_CARD_PUBLIC_KEY'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default, all requests and responses are logged using the Laravel logger.
|
||||
| Set ALFA_LOGGING_ENABLED=false to disable.
|
||||
|
|
||||
*/
|
||||
'logging' => [
|
||||
'enabled' => env('ALFA_LOGGING_ENABLED', true),
|
||||
],
|
||||
];
|
||||
// </SECTION>
|
||||
|
||||
@@ -5,10 +5,14 @@ declare(strict_types=1);
|
||||
namespace Apoloz\AlfaBank\Sbp;
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaConfig;
|
||||
use Apoloz\AlfaBank\Http\Middleware\LoggingRequestMiddleware;
|
||||
use Apoloz\AlfaBank\Http\Middleware\LoggingResponseMiddleware;
|
||||
use Apoloz\AlfaBank\Http\Middleware\RequestIdMiddleware;
|
||||
use Apoloz\AlfaBank\Http\Pagination\AlfaOffsetPaginator;
|
||||
use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling;
|
||||
use Apoloz\AlfaBank\Utils\CmsSigner;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Saloon\Http\Connector;
|
||||
use Saloon\Http\PendingRequest;
|
||||
use Saloon\Http\Request;
|
||||
@@ -27,10 +31,13 @@ class SbpConnector extends Connector implements HasPagination
|
||||
|
||||
public function __construct(
|
||||
protected AlfaConfig $alfaConfig,
|
||||
protected ?string $keyNameAlias = null
|
||||
protected ?string $keyNameAlias = null,
|
||||
protected bool $loggingEnabled = true,
|
||||
protected ?LoggerInterface $logger = null,
|
||||
) {
|
||||
$this->tries = 3;
|
||||
$this->retryInterval = 500;
|
||||
$this->logger = $this->logger ?? new NullLogger;
|
||||
}
|
||||
|
||||
public function resolveBaseUrl(): string
|
||||
@@ -64,6 +71,13 @@ class SbpConnector extends Connector implements HasPagination
|
||||
|
||||
public function boot(PendingRequest $pendingRequest): void
|
||||
{
|
||||
$pendingRequest->middleware()->onRequest(new RequestIdMiddleware);
|
||||
|
||||
if ($this->loggingEnabled) {
|
||||
$pendingRequest->middleware()->onRequest(new LoggingRequestMiddleware($this->logger));
|
||||
$pendingRequest->middleware()->onResponse(new LoggingResponseMiddleware($this->logger));
|
||||
}
|
||||
|
||||
$bodyRepo = $pendingRequest->body();
|
||||
if (! $bodyRepo) {
|
||||
return;
|
||||
@@ -107,6 +121,21 @@ class SbpConnector extends Connector implements HasPagination
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withLogging(LoggerInterface $logger): static
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->loggingEnabled = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withoutLogging(): static
|
||||
{
|
||||
$this->loggingEnabled = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function paginate(Request $request): OffsetPaginator
|
||||
{
|
||||
return new AlfaOffsetPaginator($this, $request);
|
||||
@@ -133,11 +162,4 @@ class SbpConnector extends Connector implements HasPagination
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
new RequestIdMiddleware,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user