Skip to content

Commit b1fa228

Browse files
authored
Merge pull request #1 from pascalbaljet/main
HTTP Request Middleware to log Requests after Global Middleware
2 parents 32d1493 + 05ea569 commit b1fa228

10 files changed

+218
-12
lines changed

.gitignore

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
/vendor/
1+
.phpunit.cache
2+
.phpunit.result.cache
3+
.pint.cache.json
24
/.idea
35
/composer.lock
46
/tests/_coverage
5-
.phpunit.result.cache
6-
.pint.cache.json
7+
/vendor/
8+
build
9+
coverage
10+
phpunit.xml

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# CHANGELOG
22

3-
## [v1.0.x (Unreleased)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.0.1...main)
3+
## [v1.1.x (Unreleased)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.1.0...main)
44

55
- ...
66

7+
## [v1.1.0 (2023-11-09)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.0.2...v1.1.0)
8+
9+
- Feature | HTTP Request Middleware to log Requests after Global Middleware by @pascalbaljet in #1
10+
711
## [v1.0.2 (2023-07-17)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.0.1...v1.0.2)
812

913
- Drop Laravel 9 support

README.md

+32-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,14 @@ Using the logger will log both the request and response of an external HTTP requ
5252
- **Variant 2: Mixin** (`HTTP_CLIENT_GLOBAL_LOGGER_MIXIN=true`)
5353
- Enabled only on individual HTTP Client instances, using `Http::log()` - no global logging.
5454
- Log channel name can be set per HTTP Client instance by passing a name to `Http::log($name)`
55+
- **Variant 3: Global HTTP Middleware**
56+
- Can be used in combination with other `Http::globalRequestMiddleware()` calls in your `AppServiceProvider`'s `boot()` method, after registering your [Global Middleware](https://laravel.com/docs/10.x/http-client#global-middleware).
57+
5558

5659
## Usage
5760

61+
> **NOTE:** For all 3 variants below, you need to keep the HTTP Client Global Logger enabled (not setting `HTTP_CLIENT_GLOBAL_LOGGER_ENABLED=false` in your `.env`). The `http-client-global-logger.enabled` config option is a global on/off switch for all 3 variants, not just the "global" variants. Our project name might be misleading in that context.
62+
5863
### Variant 1: Global Logging
5964

6065
**Just use Laravel HTTP Client as always - no need to configure anything!**
@@ -103,6 +108,31 @@ $client = Http::log('my-api')->withOptions([
103108
$response = $client->get('/user');
104109
```
105110

111+
### Variant 3: Global HTTP Middleware
112+
113+
If you use [Global Middleware](https://laravel.com/docs/10.x/http-client#global-middleware) (`Http::globalRequestMiddleware()` and `Http::globalResponseMiddleware()` methods), you should be aware that *Variant 1* uses Laravel's `RequestSending` event to log HTTP requests. This event is fired **before** Global Middleware is executed. Therefore, any modifications to the request made by Global Middleware will not be logged. To overcome this, this package provides a middleware that you may add after your Global Middleware.
114+
115+
You may add the middleware using the static `addRequestMiddleware()` method on the `HttpClientLogger` class:
116+
117+
```php
118+
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
119+
120+
HttpClientLogger::addRequestMiddleware();
121+
```
122+
123+
For example, you may add this to your `AppServiceProvider`'s `boot()` method after registering your Global Middleware:
124+
125+
```php
126+
use Illuminate\Support\Facades\Http;
127+
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
128+
129+
Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
130+
'User-Agent', 'My Custom User Agent'
131+
));
132+
133+
HttpClientLogger::addRequestMiddleware();
134+
```
135+
106136
## Logging example
107137

108138
By default, logs are written to a separate logfile `http-client.log`.
@@ -146,9 +176,9 @@ So, my recommendation: If you need global logging without any extra configuratio
146176
## Caveats
147177

148178
- This package currently uses two different implementations for logging. In the preferred variant 1 (global logging), it is currently not possible to configure the [log channel name](https://laravel.com/docs/logging#configuring-the-channel-name) which defaults to current environment, such as `production` or `local`. If you with to use Laravel HTTP Client to access multiple different external APIs, it is nice to explicitely distinguish between them by different log channel names.
149-
179+
150180
As a workaround, I have implemented another way of logging through `Http::log()` method as mixin. But of course, we should combine both variants into a single one for a cleaner codebase.
151-
181+
152182
- Very basic obfuscation support using regex with lookbehind assertions (e.g. `/(?<=Authorization:\sBearer ).*/m`, modifying formatted log output. It's currently not possible to directly modify request headers or JSON data in request body.
153183

154184
- Obfuscation currently only works in variant 1 (global logging).

composer.json

+20-3
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,32 @@
3434
},
3535
"require-dev": {
3636
"laravel/framework": "^10.0",
37-
"laravel/pint": "^1.10"
37+
"laravel/pint": "^1.10",
38+
"orchestra/testbench": "^8.8",
39+
"pestphp/pest": "^2.20",
40+
"pestphp/pest-plugin-arch": "^2.0",
41+
"pestphp/pest-plugin-laravel": "^2.0"
3842
},
3943
"autoload": {
4044
"psr-4": {
4145
"Onlime\\LaravelHttpClientGlobalLogger\\": "src/"
4246
}
4347
},
48+
"autoload-dev": {
49+
"psr-4": {
50+
"Onlime\\LaravelHttpClientGlobalLogger\\Tests\\": "tests/"
51+
}
52+
},
53+
"scripts": {
54+
"test": "vendor/bin/pest",
55+
"test-coverage": "vendor/bin/pest --coverage",
56+
"format": "vendor/bin/pint"
57+
},
4458
"config": {
45-
"sort-packages": true
59+
"sort-packages": true,
60+
"allow-plugins": {
61+
"pestphp/pest-plugin": true
62+
}
4663
},
4764
"extra": {
4865
"laravel": {
@@ -53,4 +70,4 @@
5370
},
5471
"minimum-stability": "stable",
5572
"prefer-stable": true
56-
}
73+
}

phpunit.xml.dist

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
5+
backupGlobals="false"
6+
bootstrap="vendor/autoload.php"
7+
colors="true"
8+
processIsolation="false"
9+
stopOnFailure="false"
10+
executionOrder="random"
11+
failOnWarning="true"
12+
failOnRisky="true"
13+
failOnEmptyTestSuite="true"
14+
beStrictAboutOutputDuringTests="true"
15+
cacheDirectory=".phpunit.cache"
16+
backupStaticProperties="false"
17+
>
18+
<testsuites>
19+
<testsuite name="VendorName Test Suite">
20+
<directory>tests</directory>
21+
</testsuite>
22+
</testsuites>
23+
<coverage>
24+
<report>
25+
<html outputDirectory="build/coverage"/>
26+
<text outputFile="build/coverage.txt"/>
27+
<clover outputFile="build/logs/clover.xml"/>
28+
</report>
29+
</coverage>
30+
<logging>
31+
<junit outputFile="build/report.junit.xml"/>
32+
</logging>
33+
<source>
34+
<include>
35+
<directory suffix=".php">./src</directory>
36+
</include>
37+
</source>
38+
</phpunit>

src/HttpClientLogger.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Onlime\LaravelHttpClientGlobalLogger;
4+
5+
use Illuminate\Http\Client\Events\RequestSending;
6+
use Illuminate\Http\Client\Request;
7+
use Illuminate\Support\Facades\Http;
8+
use Onlime\LaravelHttpClientGlobalLogger\Listeners\LogRequestSending;
9+
use Psr\Http\Message\RequestInterface;
10+
11+
/**
12+
* Use HttpClientLogger::addRequestMiddleware() to manually add the middleware
13+
* after your own middleware in AppServiceProvider::boot() or similar.
14+
*/
15+
class HttpClientLogger
16+
{
17+
private static bool $addedRequestMiddleware = false;
18+
19+
public static function addRequestMiddleware(): void
20+
{
21+
if (! config('http-client-global-logger.enabled')) {
22+
return;
23+
}
24+
25+
Http::globalRequestMiddleware(
26+
fn (RequestInterface $psrRequest) => tap($psrRequest, function (RequestInterface $psrRequest) {
27+
// Wrap PSR-7 request into Laravel's HTTP Client Request object
28+
$clientRequest = new Request($psrRequest);
29+
30+
// Instantiate event and listener
31+
$event = new RequestSending($clientRequest);
32+
$listener = new LogRequestSending;
33+
34+
// Handle event
35+
$listener->handleEvent($event);
36+
})
37+
);
38+
39+
self::$addedRequestMiddleware = true;
40+
}
41+
42+
public static function requestMiddlewareWasAdded(): bool
43+
{
44+
return self::$addedRequestMiddleware;
45+
}
46+
}

src/Listeners/LogRequestSending.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@
55
use GuzzleHttp\MessageFormatter;
66
use Illuminate\Http\Client\Events\RequestSending;
77
use Illuminate\Support\Facades\Log;
8+
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
89
use Psr\Http\Message\RequestInterface;
910

1011
class LogRequestSending
1112
{
13+
/**
14+
* Handle the event if the middleware was not added manually.
15+
*/
16+
public function handle(RequestSending $event): void
17+
{
18+
if (!HttpClientLogger::requestMiddlewareWasAdded()) {
19+
$this->handleEvent($event);
20+
}
21+
}
22+
1223
/**
1324
* Handle the event.
14-
*
15-
* @return void
1625
*/
17-
public function handle(RequestSending $event)
26+
public function handleEvent(RequestSending $event): void
1827
{
1928
$obfuscate = config('http-client-global-logger.obfuscate.enabled');
2029
$psrRequest = $event->request->toPsrRequest();

tests/HttpClientLoggerTest.php

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Http;
4+
use Illuminate\Support\Facades\Log;
5+
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
6+
use Psr\Http\Message\RequestInterface;
7+
use Psr\Log\LoggerInterface;
8+
9+
it('can add a global request middleware to log the requests', function () {
10+
Http::globalRequestMiddleware(
11+
fn (RequestInterface $psrRequest) => $psrRequest->withHeader('X-Test', 'test')
12+
);
13+
14+
HttpClientLogger::addRequestMiddleware();
15+
16+
$logger = Mockery::mock(LoggerInterface::class);
17+
18+
Log::shouldReceive('channel')->with('http-client')->andReturn($logger);
19+
20+
$logger->shouldReceive('info')->withArgs(function ($message) {
21+
expect($message)
22+
->toContain('REQUEST: GET https://example.com')
23+
->and($message)
24+
->toContain('X-Test: test');
25+
26+
return true;
27+
})->once()->andReturnSelf();
28+
29+
$logger->shouldReceive('info')->withArgs(function ($message) {
30+
expect($message)
31+
->toContain('RESPONSE: HTTP/1.1 200 OK');
32+
33+
return true;
34+
})->once()->andReturnSelf();
35+
36+
Http::fake()->get('https://example.com');
37+
});

tests/Pest.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
use Onlime\LaravelHttpClientGlobalLogger\Tests\TestCase;
4+
5+
uses(TestCase::class)->in(__DIR__);

tests/TestCase.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Onlime\LaravelHttpClientGlobalLogger\Tests;
4+
5+
use Onlime\LaravelHttpClientGlobalLogger\Providers\ServiceProvider;
6+
use Orchestra\Testbench\TestCase as Orchestra;
7+
8+
class TestCase extends Orchestra
9+
{
10+
protected function getPackageProviders($app)
11+
{
12+
return [
13+
ServiceProvider::class,
14+
];
15+
}
16+
}

0 commit comments

Comments
 (0)