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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Controls which responses are logged. Default: `0`.
| `1` | HTTP 400+ only |
| `2` | HTTP 500+ only |

Network errors (connection failures, DNS failures, etc.) are always logged regardless of this setting.
Network errors (connection failures, DNS failures, etc.) are always logged regardless of this setting. Values outside the 0–2 range are clamped to the nearest valid level.

```php
'audit_http_client_loglevel' => 1,
Expand All @@ -67,7 +67,7 @@ Every entry starts with the request ID: the ID of the server request that trigge
Plain-text example:

```
67527c3ff4b8-a3f9bc12 2026-05-05T14:23:01+00:00 GET https://example.com/feed HTTP/2 200 compressed=4821 decompressed=18944 ratio=0.25 encoding=br Hdrs=Host,Accept-Encoding,User-Agent "Nextcloud/32 ..."
gkAokgFCiaAKp30GwoTn-a3f9bc12 2026-05-05T14:23:01+00:00 GET https://example.com/feed HTTP/2 200 compressed=4821 decompressed=18944 ratio=0.25 encoding=br Hdrs=Host,Accept-Encoding,User-Agent "Nextcloud/32 ..."
```

```php
Expand Down
10 changes: 9 additions & 1 deletion lib/Http/Client/Middleware/CountingStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ public function getContents(): string {

public function __toString(): string {
try {
$seekable = $this->inner->isSeekable();
$contents = (string)$this->inner;
$this->bytesRead += strlen($contents);
if ($seekable) {
// A seekable stream rewinds for __toString, so the full body
// was delivered exactly once no matter how much was already
// read before — adding would double count.
$this->bytesRead = max($this->bytesRead, strlen($contents));
} else {
$this->bytesRead += strlen($contents);
}
return $contents;
} catch (\Throwable) {
return '';
Expand Down
4 changes: 3 additions & 1 deletion lib/Http/Client/Middleware/HttpClientLoggerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ public function __construct(
) {
$this->logger = $logger;
$this->logBaseDir = rtrim($logBaseDir, '/');
$this->logLevel = $logLevel;
// Values outside the documented 0-2 range fall back to the nearest
// valid level instead of silently behaving like "log everything".
$this->logLevel = min(max($logLevel, 0), 2);
$this->logFormat = $logFormat;
$this->excludeDomains = $excludeDomains;
$this->serverReqId = $serverReqId;
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/Http/Client/Middleware/CountingStreamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ public function testDestructLogsIncompleteStream(): void {
$this->assertStringContainsString('[stream-incomplete]', $plain);
}

public function testToStringAfterPartialReadCountsBodyOnce(): void {
$reqId = uniqid('req', true);
$stream = $this->stream('hello world', $reqId);

$this->assertSame('hello', $stream->read(5));
$this->assertSame('hello world', (string)$stream);
$stream->close();

$entries = $this->readJsonLines();
$this->assertSame(11, $entries[0]['compressionStats']['decompressed_bytes']);
}

public function testCloseTwiceWritesOnlyOneEntry(): void {
$reqId = uniqid('req', true);
$stream = $this->stream('abc', $reqId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ public function testShouldLogLevelOneLogsClientAndServerErrors(): void {
$this->assertTrue($this->invokePrivate($mw, 'shouldLog', [500]));
}

public function testShouldLogClampsOutOfRangeLevels(): void {
$tooHigh = $this->middleware(3);
$this->assertFalse($this->invokePrivate($tooHigh, 'shouldLog', [404]));
$this->assertTrue($this->invokePrivate($tooHigh, 'shouldLog', [500]));

$tooLow = $this->middleware(-1);
$this->assertTrue($this->invokePrivate($tooLow, 'shouldLog', [200]));
}

public function testShouldLogLevelTwoLogsServerErrorsOnly(): void {
$mw = $this->middleware(2);
$this->assertFalse($this->invokePrivate($mw, 'shouldLog', [200]));
Expand Down
Loading