Skip to content

Commit 4ffc5f9

Browse files
authored
Merge pull request #23 from programmatordev/OPA-9-psr-3-logger-implementation
PSR-3 logger implementation
2 parents 7b9d3d7 + 6319507 commit 4ffc5f9

File tree

6 files changed

+137
-8
lines changed

6 files changed

+137
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
/phpunit.xml
44
/.phpunit.result.cache
55
/vendor/
6+
/logs/
67
/.idea
78
/index.php

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
"authors": [
88
{
99
"name": "André Pimpão",
10-
"email": "a.pimpao@programmator.dev",
11-
"homepage": "https://programmator.dev"
10+
"email": "a.pimpao@programmator.dev"
1211
}
1312
],
1413
"require": {
@@ -18,13 +17,15 @@
1817
"psr/cache": "^3.0",
1918
"psr/http-client": "^1.0",
2019
"psr/http-factory": "^1.0",
20+
"psr/log": "^3.0",
2121
"symfony/options-resolver": "^6.3"
2222
},
2323
"provide": {
2424
"psr/http-client-implementation": "^1.0",
2525
"psr/http-factory-implementation": "^1.0"
2626
},
2727
"require-dev": {
28+
"monolog/monolog": "^3.4",
2829
"nyholm/psr7": "^1.8",
2930
"php-http/mock-client": "^1.6",
3031
"phpunit/phpunit": "^10.0",

src/Config.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
namespace ProgrammatorDev\OpenWeatherMap;
44

55
use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder;
6+
use ProgrammatorDev\OpenWeatherMap\HttpClient\Plugin\LoggerPlugin;
67
use ProgrammatorDev\OpenWeatherMap\Validator\BlankValidatorTrait;
78
use ProgrammatorDev\OpenWeatherMap\Validator\ChoiceValidatorTrait;
89
use Psr\Cache\CacheItemPoolInterface;
10+
use Psr\Log\LoggerInterface;
911
use Symfony\Component\OptionsResolver\OptionsResolver;
1012

1113
class Config
@@ -19,8 +21,9 @@ public function __construct(array $options = [])
1921
{
2022
$resolver = new OptionsResolver();
2123
$this->configureOptions($resolver);
22-
2324
$this->options = $resolver->resolve($options);
25+
26+
$this->configureAware();
2427
}
2528

2629
private function configureOptions(OptionsResolver $resolver): void
@@ -29,7 +32,8 @@ private function configureOptions(OptionsResolver $resolver): void
2932
'measurementSystem' => MeasurementSystem::METRIC,
3033
'language' => Language::ENGLISH,
3134
'httpClientBuilder' => new HttpClientBuilder(),
32-
'cache' => null
35+
'cache' => null,
36+
'logger' => null
3337
]);
3438

3539
$resolver->setRequired('applicationKey');
@@ -39,6 +43,7 @@ private function configureOptions(OptionsResolver $resolver): void
3943
$resolver->setAllowedTypes('language', 'string');
4044
$resolver->setAllowedTypes('httpClientBuilder', HttpClientBuilder::class);
4145
$resolver->setAllowedTypes('cache', ['null', CacheItemPoolInterface::class]);
46+
$resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]);
4247

4348
$resolver->setAllowedValues('applicationKey', function($value) {
4449
return !empty($value);
@@ -47,6 +52,15 @@ private function configureOptions(OptionsResolver $resolver): void
4752
$resolver->setAllowedValues('language', Language::getList());
4853
}
4954

55+
private function configureAware(): void
56+
{
57+
if ($this->getLogger() !== null) {
58+
$this->getHttpClientBuilder()->addPlugin(
59+
new LoggerPlugin($this->getLogger())
60+
);
61+
}
62+
}
63+
5064
public function getApplicationKey(): string
5165
{
5266
return $this->options['applicationKey'];
@@ -112,4 +126,16 @@ public function setCache(?CacheItemPoolInterface $cache): self
112126

113127
return $this;
114128
}
129+
130+
public function getLogger(): ?LoggerInterface
131+
{
132+
return $this->options['logger'];
133+
}
134+
135+
public function setLogger(?LoggerInterface $logger): self
136+
{
137+
$this->options['logger'] = $logger;
138+
139+
return $this;
140+
}
115141
}

src/Endpoint/AbstractEndpoint.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Psr\Http\Message\ResponseInterface;
2020
use Psr\Http\Message\StreamInterface;
2121
use Psr\Http\Message\UriInterface;
22+
use Psr\Log\LoggerInterface;
2223

2324
class AbstractEndpoint
2425
{
@@ -27,6 +28,8 @@ class AbstractEndpoint
2728

2829
private HttpMethodsClient $httpClient;
2930

31+
private ?LoggerInterface $logger;
32+
3033
private ?CacheItemPoolInterface $cache;
3134

3235
private bool $cacheInvalidation = false;
@@ -42,6 +45,7 @@ public function __construct(protected OpenWeatherMap $api)
4245
$config = $this->api->getConfig();
4346

4447
$this->httpClient = $config->getHttpClientBuilder()->getHttpClient();
48+
$this->logger = $config->getLogger();
4549
$this->cache = $config->getCache();
4650
$this->measurementSystem = $config->getMeasurementSystem();
4751
$this->language = $config->getLanguage();
@@ -70,15 +74,19 @@ protected function sendRequest(
7074
if ($this->cache !== null) {
7175
$cacheKey = $this->getCacheKey($uri);
7276

73-
// Invalidate cache (if exists) to force renewal
77+
// Invalidate cache to force new
7478
if ($this->cacheInvalidation === true) {
79+
$this->logger?->info('Cache invalidated', ['key' => $cacheKey]);
80+
7581
$this->cache->deleteItem($cacheKey);
7682
}
7783

7884
$cacheItem = $this->cache->getItem($cacheKey);
7985

80-
// If cache does not exist...
81-
if (!$cacheItem->isHit()) {
86+
if ($cacheItem->isHit()) {
87+
$this->logger?->info(\sprintf('Cache hit: %s %s', $method, $uri), ['key' => $cacheKey]);
88+
}
89+
else {
8290
$response = ResponseMediator::toArray(
8391
$this->handleRequest($method, $uri, $headers, $body)
8492
);
@@ -87,6 +95,8 @@ protected function sendRequest(
8795
$cacheItem->expiresAfter($this->cacheTtl);
8896

8997
$this->cache->save($cacheItem);
98+
99+
$this->logger?->info('Cached response', ['ttl' => $this->cacheTtl, 'key' => $cacheKey]);
90100
}
91101

92102
return $cacheItem->get();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\OpenWeatherMap\HttpClient\Plugin;
4+
5+
use Http\Client\Common\Plugin;
6+
use Http\Client\Exception;
7+
use Http\Message\Formatter;
8+
use Http\Message\Formatter\SimpleFormatter;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
use Psr\Log\LoggerInterface;
12+
13+
class LoggerPlugin implements Plugin
14+
{
15+
use Plugin\VersionBridgePlugin;
16+
17+
private LoggerInterface $logger;
18+
19+
private Formatter $formatter;
20+
21+
public function __construct(LoggerInterface $logger, Formatter $formatter = null)
22+
{
23+
$this->logger = $logger;
24+
$this->formatter = $formatter ?: new SimpleFormatter();
25+
}
26+
27+
/**
28+
* @throws Exception
29+
*/
30+
protected function doHandleRequest(RequestInterface $request, callable $next, callable $first)
31+
{
32+
$start = hrtime(true) / 1E6;
33+
$this->logger->info(
34+
\sprintf('Sending request: %s', $this->formatter->formatRequest($request))
35+
);
36+
37+
return $next($request)->then(function (ResponseInterface $response) use ($start, $request) {
38+
$milliseconds = (int) round(hrtime(true) / 1E6 - $start);
39+
$formattedResponse = $this->formatter->formatResponseForRequest($response, $request);
40+
41+
$this->logger->info(
42+
\sprintf('Received response: %s', $formattedResponse), [
43+
'milliseconds' => $milliseconds
44+
]
45+
);
46+
47+
return $response;
48+
}, function (Exception $exception) use ($request, $start) {
49+
$milliseconds = (int) round((hrtime(true) / 1E6 - $start));
50+
51+
if ($exception instanceof Exception\HttpException) {
52+
$formattedResponse = $this->formatter->formatResponseForRequest(
53+
$exception->getResponse(),
54+
$exception->getRequest()
55+
);
56+
57+
$this->logger->error(
58+
\sprintf('Error: %s with response: %s', $exception->getMessage(), $formattedResponse), [
59+
'exception' => $exception,
60+
'milliseconds' => $milliseconds
61+
]
62+
);
63+
}
64+
else {
65+
$this->logger->error(
66+
\sprintf(
67+
'Error: %s when sending request: %s',
68+
$exception->getMessage(),
69+
$this->formatter->formatRequest($request)
70+
), [
71+
'exception' => $exception,
72+
'milliseconds' => $milliseconds
73+
]
74+
);
75+
}
76+
77+
throw $exception;
78+
});
79+
}
80+
}

tests/ConfigTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
namespace ProgrammatorDev\OpenWeatherMap\Test;
44

5+
use Monolog\Logger;
56
use PHPUnit\Framework\Attributes\DataProvider;
67
use PHPUnit\Framework\Attributes\DataProviderExternal;
78
use ProgrammatorDev\OpenWeatherMap\Config;
89
use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder;
910
use ProgrammatorDev\OpenWeatherMap\Test\DataProvider\InvalidParamDataProvider;
1011
use Psr\Cache\CacheItemPoolInterface;
12+
use Psr\Log\LoggerInterface;
1113
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
1214
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
1315
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
@@ -32,6 +34,7 @@ public function testConfigDefaultOptions()
3234
$this->assertSame('en', $this->config->getLanguage());
3335
$this->assertInstanceOf(HttpClientBuilder::class, $this->config->getHttpClientBuilder());
3436
$this->assertSame(null, $this->config->getCache());
37+
$this->assertSame(null, $this->config->getLogger());
3538
}
3639

3740
public function testConfigWithOptions()
@@ -41,14 +44,16 @@ public function testConfigWithOptions()
4144
'measurementSystem' => 'imperial',
4245
'language' => 'pt',
4346
'httpClientBuilder' => new HttpClientBuilder(),
44-
'cache' => new FilesystemAdapter()
47+
'cache' => new FilesystemAdapter(),
48+
'logger' => new Logger('test')
4549
]);
4650

4751
$this->assertSame('newtestappkey', $config->getApplicationKey());
4852
$this->assertSame('imperial', $config->getMeasurementSystem());
4953
$this->assertSame('pt', $config->getLanguage());
5054
$this->assertInstanceOf(HttpClientBuilder::class, $config->getHttpClientBuilder());
5155
$this->assertInstanceOf(CacheItemPoolInterface::class, $config->getCache());
56+
$this->assertInstanceOf(LoggerInterface::class, $config->getLogger());
5257
}
5358

5459
#[DataProvider('provideInvalidConfigOptionsData')]
@@ -136,4 +141,10 @@ public function testConfigSetCache()
136141
$this->config->setCache(new FilesystemAdapter());
137142
$this->assertInstanceOf(CacheItemPoolInterface::class, $this->config->getCache());
138143
}
144+
145+
public function testConfigSetLogger()
146+
{
147+
$this->config->setLogger(new Logger('test'));
148+
$this->assertInstanceOf(LoggerInterface::class, $this->config->getLogger());
149+
}
139150
}

0 commit comments

Comments
 (0)