Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/vendor/
.phpunit.cache
.phpunit.result.cache
.pint.cache.json
/.idea
/composer.lock
/tests/_coverage
.phpunit.result.cache
.pint.cache.json
/vendor/
build
coverage
phpunit.xml
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# CHANGELOG

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

- ...

## [v1.1.0 (2023-11-09)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.0.2...v1.1.0)

- Feature | HTTP Request Middleware to log Requests after Global Middleware by @pascalbaljet in #1

## [v1.0.2 (2023-07-17)](https://github.com/onlime/laravel-http-client-global-logger/compare/v1.0.1...v1.0.2)

- Drop Laravel 9 support
Expand Down
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ Using the logger will log both the request and response of an external HTTP requ
- **Variant 2: Mixin** (`HTTP_CLIENT_GLOBAL_LOGGER_MIXIN=true`)
- Enabled only on individual HTTP Client instances, using `Http::log()` - no global logging.
- Log channel name can be set per HTTP Client instance by passing a name to `Http::log($name)`
- **Variant 3: Global HTTP Middleware**
- 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).


## Usage

> **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.

### Variant 1: Global Logging

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

### Variant 3: Global HTTP Middleware

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.

You may add the middleware using the static `addRequestMiddleware()` method on the `HttpClientLogger` class:

```php
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;

HttpClientLogger::addRequestMiddleware();
```

For example, you may add this to your `AppServiceProvider`'s `boot()` method after registering your Global Middleware:

```php
use Illuminate\Support\Facades\Http;
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;

Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
'User-Agent', 'My Custom User Agent'
));

HttpClientLogger::addRequestMiddleware();
```

## Logging example

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

- 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.

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.

- 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.

- Obfuscation currently only works in variant 1 (global logging).
Expand Down
23 changes: 20 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,32 @@
},
"require-dev": {
"laravel/framework": "^10.0",
"laravel/pint": "^1.10"
"laravel/pint": "^1.10",
"orchestra/testbench": "^8.8",
"pestphp/pest": "^2.20",
"pestphp/pest-plugin-arch": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0"
},
"autoload": {
"psr-4": {
"Onlime\\LaravelHttpClientGlobalLogger\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Onlime\\LaravelHttpClientGlobalLogger\\Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage",
"format": "vendor/bin/pint"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"extra": {
"laravel": {
Expand All @@ -53,4 +70,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
}
}
38 changes: 38 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
backupGlobals="false"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
executionOrder="random"
failOnWarning="true"
failOnRisky="true"
failOnEmptyTestSuite="true"
beStrictAboutOutputDuringTests="true"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false"
>
<testsuites>
<testsuite name="VendorName Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage>
<report>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>
46 changes: 46 additions & 0 deletions src/HttpClientLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Onlime\LaravelHttpClientGlobalLogger;

use Illuminate\Http\Client\Events\RequestSending;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Onlime\LaravelHttpClientGlobalLogger\Listeners\LogRequestSending;
use Psr\Http\Message\RequestInterface;

/**
* Use HttpClientLogger::addRequestMiddleware() to manually add the middleware
* after your own middleware in AppServiceProvider::boot() or similar.
*/
class HttpClientLogger
{
private static bool $addedRequestMiddleware = false;

public static function addRequestMiddleware(): void
{
if (! config('http-client-global-logger.enabled')) {
return;
}

Http::globalRequestMiddleware(
fn (RequestInterface $psrRequest) => tap($psrRequest, function (RequestInterface $psrRequest) {
// Wrap PSR-7 request into Laravel's HTTP Client Request object
$clientRequest = new Request($psrRequest);

// Instantiate event and listener
$event = new RequestSending($clientRequest);
$listener = new LogRequestSending;

// Handle event
$listener->handleEvent($event);
})
);

self::$addedRequestMiddleware = true;
}

public static function requestMiddlewareWasAdded(): bool
{
return self::$addedRequestMiddleware;
}
}
15 changes: 12 additions & 3 deletions src/Listeners/LogRequestSending.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,25 @@
use GuzzleHttp\MessageFormatter;
use Illuminate\Http\Client\Events\RequestSending;
use Illuminate\Support\Facades\Log;
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
use Psr\Http\Message\RequestInterface;

class LogRequestSending
{
/**
* Handle the event if the middleware was not added manually.
*/
public function handle(RequestSending $event): void
{
if (!HttpClientLogger::requestMiddlewareWasAdded()) {
$this->handleEvent($event);
}
}

/**
* Handle the event.
*
* @return void
*/
public function handle(RequestSending $event)
public function handleEvent(RequestSending $event): void
{
$obfuscate = config('http-client-global-logger.obfuscate.enabled');
$psrRequest = $event->request->toPsrRequest();
Expand Down
37 changes: 37 additions & 0 deletions tests/HttpClientLoggerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Onlime\LaravelHttpClientGlobalLogger\HttpClientLogger;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;

it('can add a global request middleware to log the requests', function () {
Http::globalRequestMiddleware(
fn (RequestInterface $psrRequest) => $psrRequest->withHeader('X-Test', 'test')
);

HttpClientLogger::addRequestMiddleware();

$logger = Mockery::mock(LoggerInterface::class);

Log::shouldReceive('channel')->with('http-client')->andReturn($logger);

$logger->shouldReceive('info')->withArgs(function ($message) {
expect($message)
->toContain('REQUEST: GET https://example.com')
->and($message)
->toContain('X-Test: test');

return true;
})->once()->andReturnSelf();

$logger->shouldReceive('info')->withArgs(function ($message) {
expect($message)
->toContain('RESPONSE: HTTP/1.1 200 OK');

return true;
})->once()->andReturnSelf();

Http::fake()->get('https://example.com');
});
5 changes: 5 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

use Onlime\LaravelHttpClientGlobalLogger\Tests\TestCase;

uses(TestCase::class)->in(__DIR__);
16 changes: 16 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Onlime\LaravelHttpClientGlobalLogger\Tests;

use Onlime\LaravelHttpClientGlobalLogger\Providers\ServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;

class TestCase extends Orchestra
{
protected function getPackageProviders($app)
{
return [
ServiceProvider::class,
];
}
}