Skip to content

Commit 1b61a5f

Browse files
authored
Merge pull request #211 from laminas/feature/log-http-responses
Log HTTP requests/responses (only non-sensitive data)
2 parents e5e024e + d458075 commit 1b61a5f

13 files changed

+371
-31
lines changed

bin/console.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
use Laminas\AutomaticReleases\Github\JwageGenerateChangelog;
3838
use Laminas\AutomaticReleases\Github\MergeMultipleReleaseNotes;
3939
use Laminas\AutomaticReleases\Gpg\ImportGpgKeyFromStringViaTemporaryFile;
40+
use Laminas\AutomaticReleases\HttpClient\LoggingHttpClient;
41+
use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpRequestsIntoStrings;
42+
use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpResponsesIntoStrings;
4043
use Lcobucci\Clock\SystemClock;
4144
use Monolog\Handler\StreamHandler;
4245
use Monolog\Logger;
@@ -60,14 +63,20 @@ static function (int $errorCode, string $message = '', string $file = '', int $l
6063
E_STRICT | E_NOTICE | E_WARNING,
6164
);
6265

63-
$variables = EnvironmentVariables::fromEnvironment(new ImportGpgKeyFromStringViaTemporaryFile());
64-
$logger = new Logger('automatic-releases');
65-
$logger->pushHandler(new StreamHandler(STDERR, $variables->logLevel()));
66+
$variables = EnvironmentVariables::fromEnvironment(new ImportGpgKeyFromStringViaTemporaryFile());
67+
$logger = new Logger(
68+
'automatic-releases',
69+
[new StreamHandler(STDERR, $variables->logLevel())],
70+
[
71+
new ConvertLogContextHttpRequestsIntoStrings(),
72+
new ConvertLogContextHttpResponsesIntoStrings(),
73+
],
74+
);
6675
$loadEvent = new LoadCurrentGithubEventFromGithubActionPath($variables);
6776
$fetch = new FetchAndSetCurrentUserByReplacingCurrentOriginRemote($variables);
6877
$getCandidateBranches = new GetMergeTargetCandidateBranchesFromRemoteBranches();
6978
$makeRequests = Psr17FactoryDiscovery::findRequestFactory();
70-
$httpClient = HttpClientDiscovery::find();
79+
$httpClient = new LoggingHttpClient(HttpClientDiscovery::find(), $logger);
7180
$githubToken = $variables->githubToken();
7281
$getMilestone = new GetMilestoneFirst100IssuesAndPullRequests(new RunGraphQLQuery(
7382
$makeRequests,

infection.json.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"mutators": {
1313
"@default": true
1414
},
15-
"minMsi": 97,
15+
"minMsi": 98,
1616
"minCoveredMsi": 100
1717
}

psalm.xml.dist

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,6 @@
1515
</ignoreFiles>
1616
</projectFiles>
1717

18-
<issueHandlers>
19-
<InternalMethod>
20-
<errorLevel type="suppress">
21-
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::method"/>
22-
</errorLevel>
23-
<errorLevel type="suppress">
24-
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::willReturn"/>
25-
</errorLevel>
26-
<errorLevel type="suppress">
27-
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::with"/>
28-
</errorLevel>
29-
</InternalMethod>
30-
</issueHandlers>
3118
<plugins>
3219
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
3320
<pluginClass class="Psl\Psalm\Plugin"/>

src/Changelog/ChangelogReleaseNotes.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public function merge(self $next): self
5858
{
5959
if ($this->changelogEntry && $next->changelogEntry) {
6060
throw new RuntimeException(
61-
'Aborting: Both current release notes and next contain a ChangelogEntry;'
62-
. ' only one CreateReleaseText implementation should resolve one.',
61+
'Aborting: Both current release notes and next contain a ChangelogEntry; only one CreateReleaseText implementation should resolve one.',
6362
);
6463
}
6564

src/Git/Value/MergeTargetCandidateBranches.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ public static function fromAllBranches(BranchName ...$branches): self
2626
return $branch->isReleaseBranch();
2727
});
2828

29-
$mergeTargetBranches = Vec\sort($mergeTargetBranches, static function (BranchName $a, BranchName $b): int {
30-
return $a->majorAndMinor() <=> $b->majorAndMinor();
31-
});
29+
$mergeTargetBranches = Vec\sort($mergeTargetBranches, self::branchOrder(...));
3230

3331
return new self($mergeTargetBranches);
3432
}
@@ -98,4 +96,10 @@ public function contains(BranchName $needle): bool
9896
static fn (BranchName $branch): bool => $needle->equals($branch)
9997
);
10098
}
99+
100+
/** @return -1|0|1 */
101+
private static function branchOrder(BranchName $a, BranchName $b): int
102+
{
103+
return $a->majorAndMinor() <=> $b->majorAndMinor();
104+
}
101105
}

src/Github/CreateReleaseTextViaKeepAChangelog.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,20 @@ private function updateReleaseDate(string $changelog, string $version): string
130130
*/
131131
private function removeDefaultContents(string $changelog): string
132132
{
133-
$contents = Iter\reduce(
133+
return Type\non_empty_string()->assert(Iter\reduce(
134134
self::DEFAULT_SECTIONS,
135-
static fn (string $changelog, string $section): string => Regex\replace(
136-
$changelog,
137-
"/\n\#{3} " . $section . "\n\n- Nothing.\n/s",
138-
'',
139-
),
135+
self::removeEmptyDefaultChangelogSection(...),
140136
$changelog,
141-
);
137+
));
138+
}
142139

143-
return Type\non_empty_string()->assert($contents);
140+
private static function removeEmptyDefaultChangelogSection(string $changelog, string $section): string
141+
{
142+
return Regex\replace(
143+
$changelog,
144+
"/\n\#{3} " . $section . "\n\n- Nothing.\n/s",
145+
'',
146+
);
144147
}
145148

146149
/**

src/HttpClient/LoggingHttpClient.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\HttpClient;
6+
7+
use Psr\Http\Client\ClientInterface;
8+
use Psr\Http\Message\RequestInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Log\LoggerInterface;
11+
12+
/** @internal */
13+
final class LoggingHttpClient implements ClientInterface
14+
{
15+
public function __construct(private readonly ClientInterface $next, private readonly LoggerInterface $logger)
16+
{
17+
}
18+
19+
public function sendRequest(RequestInterface $request): ResponseInterface
20+
{
21+
$this->logger->debug('Sending request {request}', ['request' => $request]);
22+
23+
$response = $this->next->sendRequest($request);
24+
25+
$this->logger->debug(
26+
'Received response {response} to request {request}',
27+
[
28+
'request' => $request,
29+
'response' => $response,
30+
],
31+
);
32+
33+
return $response;
34+
}
35+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Monolog;
6+
7+
use Monolog\LogRecord;
8+
use Monolog\Processor\ProcessorInterface;
9+
use Psr\Http\Message\RequestInterface;
10+
11+
use function array_map;
12+
13+
/** @internal */
14+
final class ConvertLogContextHttpRequestsIntoStrings implements ProcessorInterface
15+
{
16+
public function __invoke(LogRecord $record): LogRecord
17+
{
18+
return new LogRecord(
19+
$record->datetime,
20+
$record->channel,
21+
$record->level,
22+
$record->message,
23+
array_map(self::contextItemToMessage(...), $record->context),
24+
$record->extra,
25+
$record->formatted,
26+
);
27+
}
28+
29+
private static function contextItemToMessage(mixed $item): mixed
30+
{
31+
if (! $item instanceof RequestInterface) {
32+
return $item;
33+
}
34+
35+
return $item->getMethod()
36+
. ' '
37+
. $item
38+
->getUri()
39+
->withUserInfo('')
40+
->__toString();
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Monolog;
6+
7+
use Monolog\LogRecord;
8+
use Monolog\Processor\ProcessorInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
use function array_map;
12+
13+
/** @internal */
14+
final class ConvertLogContextHttpResponsesIntoStrings implements ProcessorInterface
15+
{
16+
public function __invoke(LogRecord $record): LogRecord
17+
{
18+
return new LogRecord(
19+
$record->datetime,
20+
$record->channel,
21+
$record->level,
22+
$record->message,
23+
array_map(self::contextItemToMessage(...), $record->context),
24+
$record->extra,
25+
$record->formatted,
26+
);
27+
}
28+
29+
private static function contextItemToMessage(mixed $item): mixed
30+
{
31+
if (! $item instanceof ResponseInterface) {
32+
return $item;
33+
}
34+
35+
return $item->getStatusCode()
36+
. ' "'
37+
. $item
38+
->getBody()
39+
->__toString()
40+
. '"';
41+
}
42+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Test\Unit\HttpClient;
6+
7+
use Http\Discovery\Psr17FactoryDiscovery;
8+
use Laminas\AutomaticReleases\HttpClient\LoggingHttpClient;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Http\Client\ClientInterface;
11+
use Psr\Log\LoggerInterface;
12+
13+
/** @covers \Laminas\AutomaticReleases\HttpClient\LoggingHttpClient */
14+
final class LoggingHttpClientTest extends TestCase
15+
{
16+
public function testWillLogRequestAndResponse(): void
17+
{
18+
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('get', 'http://example.com/foo/bar');
19+
$response = Psr17FactoryDiscovery::findResponseFactory()->createResponse(204);
20+
21+
$response->getBody()
22+
->write('hello world');
23+
24+
$logger = $this->createMock(LoggerInterface::class);
25+
$next = $this->createMock(ClientInterface::class);
26+
27+
$next->expects(self::once())
28+
->method('sendRequest')
29+
->with($request)
30+
->willReturn($response);
31+
32+
$logger->expects(self::exactly(2))
33+
->method('debug')
34+
->withConsecutive(
35+
['Sending request {request}', ['request' => $request]],
36+
[
37+
'Received response {response} to request {request}',
38+
[
39+
'request' => $request,
40+
'response' => $response,
41+
],
42+
],
43+
);
44+
45+
self::assertSame(
46+
$response,
47+
(new LoggingHttpClient($next, $logger))
48+
->sendRequest($request),
49+
);
50+
}
51+
}

0 commit comments

Comments
 (0)