Skip to content

Commit dcff878

Browse files
committed
Added option to enforce trimming of response body by setting a X-Global-Logger-Trim-Always request header
1 parent c7c91d5 commit dcff878

File tree

6 files changed

+59
-21
lines changed

6 files changed

+59
-21
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
## [v1.2.0 (2024-10-15)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.1.3...v1.2.0)
66

7+
- Feature | You can now enforce trimming of response body by setting a `X-Global-Logger-Trim-Always` request header, which will ignore the Content-Type whitelisting.
78
- Drop PHP 8.1 support
89
- Upgrade pestphp/pest to v3
910

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ Using the logger will log both the request and response of an external HTTP requ
4949
- Multi-line log records that contain full request/response information (including all headers and body)
5050
- Logging into separate logfile `http-client.log`. You're free to override this and use your own logging channel or just log to a different logfile.
5151
- Full support of [Guzzle MessageFormatter](https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php) variable substitutions for highly customized log messages.
52-
- Basic obfuscation of credentials in HTTP Client requests
53-
- Trimming of response body content to a certain length with support for Content-Type whitelisting
52+
- Basic obfuscation of credentials in HTTP Client requests (both by header or body keys)
53+
- Trimming of response body content to a certain length with support for `Content-Type` whitelisting
54+
- Enforce trimming of response body content by setting a `X-Global-Logger-Trim-Always` request header, which will ignore the whitelisting.
5455
- **Variant 1: Global logging** (default)
5556
- Zero-configuration: Global logging is enabled by default in this package.
5657
- Simple and performant implementation using `RequestSending` / `ResponseReceived` event listeners

config/http-client-global-logger.php

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
|
119119
| NOTE the leading comma in trim_response_body.content_type_whitelist default value:
120120
| it's there to whitelist empty content types (e.g. when no Content-Type header is set).
121+
|
122+
| The content type whitelisting can be ignored by setting the following header in the
123+
| request: X-Global-Logger-Trim-Always (set it to any value, e.g. 'true').
121124
*/
122125
'trim_response_body' => [
123126
'enabled' => env('HTTP_CLIENT_GLOBAL_LOGGER_TRIM_RESPONSE_BODY_ENABLED', false),

src/EventHelper.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static function getPsrRequest(object $event): RequestInterface
3131
return match (true) {
3232
$event instanceof RequestSending || $event instanceof ResponseReceived => $event->request->toPsrRequest(),
3333
$event instanceof SendingSaloonRequest || $event instanceof SentSaloonRequest => $event->pendingRequest->createPsrRequest(),
34-
default => throw new Exception('Can not get PSR Request from Event: '.get_class($event)),
34+
default => throw new Exception('Cannot get PSR Request from Event: '.get_class($event)),
3535
};
3636
}
3737

@@ -40,7 +40,7 @@ public static function getPsrResponse(object $event): ResponseInterface
4040
return match (true) {
4141
$event instanceof ResponseReceived => $event->response->toPsrResponse(),
4242
$event instanceof SentSaloonRequest => $event->response->getPsrResponse(),
43-
default => throw new Exception('Can not get PSR Response from Event: '.get_class($event)),
43+
default => throw new Exception('Cannot get PSR Response from Event: '.get_class($event)),
4444
};
4545
}
4646
}

src/Listeners/LogResponseReceived.php

+22-16
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,43 @@ public function handle(ResponseReceived|SentSaloonRequest $event): void
2424
}
2525

2626
$formatter = new MessageFormatter(config('http-client-global-logger.format.response'));
27+
$psrRequest = EventHelper::getPsrRequest($event);
2728
Log::channel(config('http-client-global-logger.channel'))->info($formatter->format(
28-
EventHelper::getPsrRequest($event),
29-
$this->trimBody(EventHelper::getPsrResponse($event))
29+
$psrRequest,
30+
$this->trimBody(
31+
EventHelper::getPsrResponse($event),
32+
$psrRequest->hasHeader('X-Global-Logger-Trim-Always')
33+
)
3034
));
3135
}
3236

3337
/**
3438
* Trim the response body when it's too long.
3539
*/
36-
private function trimBody(Response $psrResponse): Response|MessageInterface
40+
private function trimBody(Response $psrResponse, bool $trimAlways = false): Response|MessageInterface
3741
{
3842
// Check if trimming is enabled
3943
if (! config('http-client-global-logger.trim_response_body.enabled')) {
4044
return $psrResponse;
4145
}
4246

43-
// E.g.: application/json; charset=utf-8 => application/json
44-
$contentTypeHeader = Str::of($psrResponse->getHeaderLine('Content-Type'))
45-
->before(';')
46-
->trim()
47-
->lower()
48-
->value();
47+
if (! $trimAlways) {
48+
// E.g.: application/json; charset=utf-8 => application/json
49+
$contentTypeHeader = Str::of($psrResponse->getHeaderLine('Content-Type'))
50+
->before(';')
51+
->trim()
52+
->lower()
53+
->value();
4954

50-
$whiteListedContentTypes = array_map(
51-
fn (string $type) => trim(strtolower($type)),
52-
config('http-client-global-logger.trim_response_body.content_type_whitelist')
53-
);
55+
$whiteListedContentTypes = array_map(
56+
fn (string $type) => trim(strtolower($type)),
57+
config('http-client-global-logger.trim_response_body.content_type_whitelist')
58+
);
5459

55-
// Check if the content type is whitelisted
56-
if (in_array($contentTypeHeader, $whiteListedContentTypes)) {
57-
return $psrResponse;
60+
// Check if the content type is whitelisted
61+
if (in_array($contentTypeHeader, $whiteListedContentTypes)) {
62+
return $psrResponse;
63+
}
5864
}
5965

6066
$limit = config('http-client-global-logger.trim_response_body.limit');

tests/HttpClientLoggerTest.php

+28-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function setupLogger(): MockInterface
4444
Http::fake()->get('https://example.com');
4545
});
4646

47-
it('can trim the body response', function (array $config, string $contentType, bool $shouldTrim, bool $addCharsetToContentType) {
47+
it('can trim the response body', function (array $config, string $contentType, bool $shouldTrim, bool $addCharsetToContentType) {
4848
config(['http-client-global-logger.trim_response_body' => $config]);
4949

5050
$logger = setupLogger();
@@ -126,3 +126,30 @@ function setupLogger(): MockInterface
126126
'without-charset' => ['addCharsetToContentType' => false],
127127
]
128128
);
129+
130+
it('always trims the response body when special header is set on request', function (bool $withHeader) {
131+
config(['http-client-global-logger.trim_response_body' => [
132+
'enabled' => true,
133+
'limit' => 10,
134+
'content_type_whitelist' => ['application/json'],
135+
]]);
136+
137+
$logger = setupLogger();
138+
139+
$logger->shouldReceive('info')->withArgs(function ($message) {
140+
expect($message)->toContain('REQUEST: GET https://example.com');
141+
return true;
142+
})->once();
143+
144+
$logger->shouldReceive('info')->withArgs(function ($message) use ($withHeader) {
145+
expect($message)->toContain($withHeader ? 'verylongbo...' : 'verylongbody');
146+
return true;
147+
})->once();
148+
149+
Http::fake([
150+
'*' => Http::response('verylongbody', 200, [
151+
'Content-Type' => 'application/json',
152+
]),
153+
])->withHeader($withHeader ? 'X-Global-Logger-Trim-Always' : 'X-Dummy', 'true')
154+
->get('https://example.com');
155+
})->with([true, false]);

0 commit comments

Comments
 (0)