Skip to content

Commit 0471dd7

Browse files
committed
By default log failed requests to a file + test improvments
- Do not catch PHPUnits exceptions - Add base tests for Laravel to ensure all classes can be created by container
1 parent 11e2801 commit 0471dd7

19 files changed

+409
-26
lines changed

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"league/flysystem": "^3.14",
2828
"mockery/mockery": "^1.5.1",
2929
"nyholm/psr7": "^1.8.0",
30+
"orchestra/testbench": "^7.26",
31+
"php-http/mock-client": "^1.6",
3032
"phpstan/phpstan": "1.10.26",
3133
"phpstan/phpstan-deprecation-rules": "^1.1.3",
3234
"phpstan/phpstan-mockery": "^1.1.1",
@@ -40,6 +42,9 @@
4042
"wrkflow/larastrict": "Improve your Laravel code base with strict conventions."
4143
},
4244
"scripts": {
45+
"post-autoload-dump": [
46+
"@php vendor/bin/testbench package:discover --ansi"
47+
],
4348
"check": "composer lint && composer test",
4449
"lint:check": "./vendor/bin/ecs",
4550
"lint:fix": "./vendor/bin/ecs --fix",

phpunit.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@
1717
<directory>./tests/</directory>
1818
</testsuite>
1919
</testsuites>
20+
<php>
21+
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
22+
<env name="DB_CONNECTION" value="testing"/>
23+
</php>
24+
2025
</phpunit>

src/Actions/SendRequestAction.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function execute(
9797
}
9898

9999
/**
100-
* @param class-string<AbstractResponse> $responseClass
100+
* @param class-string<AbstractResponse> $responseClass
101101
*/
102102
private function sendRequest(
103103
AbstractEnvironment $environment,
@@ -127,6 +127,8 @@ private function sendRequest(
127127
->client()
128128
->sendRequest($request);
129129
} catch (Exception $exception) {
130+
$this->rethrowIfPHPUnitException($exception);
131+
130132
$event = new RequestConnectionFailedEvent(
131133
id: $requestId,
132134
request: $request,
@@ -167,7 +169,7 @@ private function getRequestDuration(float $timeStart): float
167169
* @template TResponse of AbstractResponse
168170
*
169171
* @param class-string<TResponse> $responseClass
170-
* @param int|null $expectedResponseStatusCode Will raise and failed
172+
* @param int|null $expectedResponseStatusCode Will raise and failed
171173
* exception if response
172174
* status code is different
173175
*
@@ -211,6 +213,8 @@ private function handleResponse(
211213

212214
throw $api->createFailedResponseException($statusCode, $response);
213215
} catch (Exception $exception) {
216+
$this->rethrowIfPHPUnitException($exception);
217+
214218
// Wrap any custom exception to response exception because we have a response
215219
if ($exception instanceof ResponseException === false) {
216220
$exception = new ResponseException($response, $exception->getMessage(), $exception);
@@ -241,4 +245,17 @@ private function buildRequest(
241245
$request = $this->buildHeadersAction->execute($mergedHeaders, $request);
242246
return $this->withBody($api, $body, $request);
243247
}
248+
249+
/**
250+
* If we are using assert mocking, we do not want to log the exception and
251+
* rethrow it.
252+
*/
253+
private function rethrowIfPHPUnitException(Exception $exception): void
254+
{
255+
// PHPUnit can be missing.
256+
$class = '\\' . \PHPUnit\Exception::class;
257+
if ($exception instanceof $class) {
258+
throw $exception;
259+
}
260+
}
244261
}

src/Laravel/Configs/api_sdk.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* - Log multiple domains: 'domain.com:debug,otherdomain.com:info',
2121
* - Log all except given domain: '*:info,domain.com:'
2222
*/
23-
ApiSdkConfig::KeyLoggingType => env('API_SDK_LOGGING', '*:info'),
23+
ApiSdkConfig::KeyLoggingType => env('API_SDK_LOGGING', '*:info_file'),
2424
/**
2525
* List of available loggers key-ed by their name and their implementation.
2626
*

src/Laravel/LaravelServiceProvider.php

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use League\Flysystem\FilesystemOperator;
2020
use LogicException;
2121
use Psr\EventDispatcher\EventDispatcherInterface;
22-
use Psr\Log\LoggerInterface;
2322
use WrkFlow\ApiSdkBuilder\Actions\MakeApiFactory;
2423
use WrkFlow\ApiSdkBuilder\Contracts\ApiFactoryContract;
2524
use WrkFlow\ApiSdkBuilder\Contracts\SDKContainerFactoryContract;
@@ -30,20 +29,22 @@
3029
use WrkFlow\ApiSdkBuilder\Laravel\Configs\ApiSdkConfig;
3130
use WrkFlow\ApiSdkBuilder\Log\Actions\BuildRequestHttpFileAction;
3231
use WrkFlow\ApiSdkBuilder\Log\Actions\ClearFileLogsAction;
33-
use WrkFlow\ApiSdkBuilder\Log\Actions\GetExtensionFromContentTypeAction;
3432
use WrkFlow\ApiSdkBuilder\Log\Contracts\BuildRequestLogFileActionContract;
3533
use WrkFlow\ApiSdkBuilder\Log\Contracts\DebugLoggerContract;
3634
use WrkFlow\ApiSdkBuilder\Log\Contracts\FileLoggerContract;
3735
use WrkFlow\ApiSdkBuilder\Log\Contracts\FileLogPathServiceContract;
3836
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoLoggerContract;
37+
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoOrFailFileLoggerContract;
3938
use WrkFlow\ApiSdkBuilder\Log\Entities\LoggerConfigEntity;
4039
use WrkFlow\ApiSdkBuilder\Log\Loggers\DebugLogger;
4140
use WrkFlow\ApiSdkBuilder\Log\Loggers\FileLogger;
4241
use WrkFlow\ApiSdkBuilder\Log\Loggers\InfoLogger;
42+
use WrkFlow\ApiSdkBuilder\Log\Loggers\InfoOrFailFileLogger;
4343
use WrkFlow\ApiSdkBuilder\Log\Services\FileLogPathService;
4444

4545
class LaravelServiceProvider extends ServiceProvider
4646
{
47+
final public const KeyFilesystemOperator = 'api_sdk_filesystem_operator';
4748
private const ConfigPath = __DIR__ . '/Configs/api_sdk.php';
4849
private const ConfigKey = 'api_sdk';
4950

@@ -70,8 +71,9 @@ public function boot(): void
7071
$clearAt = $config->getTimeForClearSchedule();
7172

7273
if ($clearAt !== null) {
73-
/** @var Schedule $schedule */
7474
$schedule = $this->app->make(Schedule::class);
75+
assert($schedule instanceof Schedule);
76+
7577
$schedule->command(ClearFileLogsCommand::class)
7678
->dailyAt($clearAt);
7779
}
@@ -105,8 +107,8 @@ protected function bindApi(): void
105107
$this->app->singleton(
106108
abstract: ApiFactoryContract::class,
107109
concrete: static function (Container $container): ApiFactoryContract {
108-
/** @var MakeApiFactory $makeApiFactory */
109110
$makeApiFactory = $container->make(MakeApiFactory::class);
111+
assert($makeApiFactory instanceof MakeApiFactory);
110112

111113
return $makeApiFactory->execute(loggerConfig: $container->make(LoggerConfigEntity::class));
112114
}
@@ -119,27 +121,33 @@ protected function bindLogs(): void
119121
abstract: BuildRequestLogFileActionContract::class,
120122
concrete: BuildRequestHttpFileAction::class
121123
);
124+
$this->app->singleton(abstract: ClearFileLogsAction::class, concrete: ClearFileLogsAction::class);
122125
$this->app->singleton(abstract: FileLogPathServiceContract::class, concrete: FileLogPathService::class);
123126
$this->app->singleton(abstract: DebugLoggerContract::class, concrete: DebugLogger::class);
124127
$this->app->singleton(abstract: FileLoggerContract::class, concrete: FileLogger::class);
125128
$this->app->singleton(abstract: InfoLoggerContract::class, concrete: InfoLogger::class);
129+
$this->app->singleton(abstract: InfoOrFailFileLoggerContract::class, concrete: InfoOrFailFileLogger::class);
126130

127-
$this->app->singleton(
128-
abstract: FileLogger::class,
129-
concrete: static fn (Application $application) => new FileLogger(
130-
filesystemOperator: static::getFileSystemOperator($application),
131-
logger: $application->make(LoggerInterface::class),
132-
fileLogPathService: $application->make(FileLogPathService::class),
133-
getExtensionFromContentTypeAction: $application->make(GetExtensionFromContentTypeAction::class),
134-
buildRequestLogFileAction: $application->make(BuildRequestLogFileActionContract::class),
135-
)
131+
// Not sure if we provided FilesystemOperator to whole Laravel application
132+
// if it would break some package usage... So lets be explicit.
133+
$this->app->bind(
134+
abstract: self::KeyFilesystemOperator,
135+
concrete: static fn (Application $application) => static::getFileSystemOperator($application)
136136
);
137-
138-
$this->app->bind(ClearFileLogsAction::class, static fn (Application $application) => new ClearFileLogsAction(
139-
filesystemOperator: static::getFileSystemOperator($application),
140-
fileLogPathService: $application->make(FileLogPathService::class),
141-
logger: $application->make(LoggerInterface::class),
142-
));
137+
$this->app
138+
->when(FileLogger::class)
139+
->needs(FilesystemOperator::class)
140+
->give(self::KeyFilesystemOperator);
141+
142+
$this->app
143+
->when(FileLogger::class)
144+
->needs(FilesystemOperator::class)
145+
->give(self::KeyFilesystemOperator);
146+
147+
$this->app
148+
->when(ClearFileLogsAction::class)
149+
->needs(FilesystemOperator::class)
150+
->give(self::KeyFilesystemOperator);
143151
}
144152

145153
protected function getEventDispatcher(): Dispatcher

src/Log/Constants/LoggerConstants.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@
77
use WrkFlow\ApiSdkBuilder\Log\Contracts\DebugLoggerContract;
88
use WrkFlow\ApiSdkBuilder\Log\Contracts\FileLoggerContract;
99
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoLoggerContract;
10+
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoOrFailFileLoggerContract;
11+
use WrkFlow\ApiSdkBuilder\Log\Contracts\LoggerContract;
1012

1113
class LoggerConstants
1214
{
15+
/**
16+
* @var array<string, array<class-string<LoggerContract>>>
17+
*/
1318
final public const DefaultLoggersMap = [
1419
self::NoLog => [],
1520
self::Info => [InfoLoggerContract::class],
1621
self::Debug => [DebugLoggerContract::class],
1722
self::File => [InfoLoggerContract::class, FileLoggerContract::class],
23+
self::InfoOrFailFile => [InfoOrFailFileLoggerContract::class],
1824
];
1925
final public const NoLog = '';
2026
final public const Info = 'info';
27+
final public const InfoOrFailFile = 'info_file';
2128
final public const Debug = 'debug';
2229
final public const File = 'file';
2330
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WrkFlow\ApiSdkBuilder\Log\Contracts;
6+
7+
interface InfoOrFailFileLoggerContract extends LoggerContract
8+
{
9+
}

src/Log/Loggers/InfoLogger.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ private function debug(
6464
? 'FAILED'
6565
: (string) $response->getStatusCode();
6666

67-
// example.com GET 200 /api/v1/clients/1 [20s]
68-
// example.com GET 500 /api/v1/clients/1 [20s]
69-
// example.com GET FAILED /api/v1/clients/1 [20s]
67+
// GET 200 example.com /api/v1/clients/1 [20s]
68+
// GET example.com 500 /api/v1/clients/1 [20s]
69+
// GET example.com FAILED /api/v1/clients/1 [20s]
7070
$this->logger->info(
7171
message: sprintf(
7272
'%s %s %s %s [%ds]',
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WrkFlow\ApiSdkBuilder\Log\Loggers;
6+
7+
use WrkFlow\ApiSdkBuilder\Events\RequestConnectionFailedEvent;
8+
use WrkFlow\ApiSdkBuilder\Events\RequestFailedEvent;
9+
use WrkFlow\ApiSdkBuilder\Events\ResponseReceivedEvent;
10+
use WrkFlow\ApiSdkBuilder\Log\Contracts\FileLoggerContract;
11+
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoLoggerContract;
12+
use WrkFlow\ApiSdkBuilder\Log\Contracts\InfoOrFailFileLoggerContract;
13+
use WrkFlow\ApiSdkBuilder\Log\Entities\LoggerConfigEntity;
14+
15+
class InfoOrFailFileLogger implements InfoOrFailFileLoggerContract
16+
{
17+
public function __construct(
18+
private readonly InfoLoggerContract $logger,
19+
private readonly FileLoggerContract $fileLogger
20+
) {
21+
}
22+
23+
public function responseReceivedEvent(ResponseReceivedEvent $event, LoggerConfigEntity $config): void
24+
{
25+
$this->logger->responseReceivedEvent($event, $config);
26+
}
27+
28+
public function requestFailed(RequestFailedEvent $event, LoggerConfigEntity $config): void
29+
{
30+
$this->logger->requestFailed($event, $config);
31+
$this->fileLogger->requestFailed($event, $config);
32+
}
33+
34+
public function requestConnectionFailed(RequestConnectionFailedEvent $event, LoggerConfigEntity $config): void
35+
{
36+
$this->logger->requestConnectionFailed($event, $config);
37+
$this->fileLogger->requestConnectionFailed($event, $config);
38+
}
39+
}

tests/Actions/BuildHeadersActionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Wrkflow\ApiSdkBuilderTests\Actions;
5+
namespace WrkFlow\ApiSdkBuilderTests\Actions;
66

77
use Nyholm\Psr7\Request;
88
use PHPUnit\Framework\TestCase;

0 commit comments

Comments
 (0)