Skip to content

Commit c987540

Browse files
authored
Merge pull request #39 from ingenerator/1.x-log-request-memory
Improve request logging - memory usage & max user-agent length
2 parents 99817e7 + 002ffb2 commit c987540

File tree

4 files changed

+65
-5
lines changed

4 files changed

+65
-5
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
### Unreleased
22

3+
### v1.14.0 (2021-11-19)
4+
5+
* StackdriverApplicationLogger: limit length of user-agent string in request logs to a maximum of 500 bytes.
6+
* Now requires the `mbstring` PHP extension to be present.
7+
* StackdriverApplicationLogger: add the peak memory usage to the context info of the request logger (context.mem_mb).
38

49
### v1.13.0 (2021-11-04)
510

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
],
1717
"require": {
1818
"ext-json": "*",
19+
"ext-mbstring": "^7 || ~8.0.0",
1920
"ext-pdo": "^7 || ~8.0.0",
2021
"ext-sodium": "^7.4 || ~8.0.0",
2122
"php": "^7.4 || ~8.0.0",

src/Logging/StackdriverApplicationLogger.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
use function http_response_code;
1818
use function is_callable;
1919
use function json_encode;
20+
use function mb_strcut;
2021
use function microtime;
2122
use function sprintf;
23+
use function strlen;
2224
use function strtoupper;
2325
use function substr;
2426

@@ -377,14 +379,17 @@ public function logRequest(?array $server, ?float $request_start_time = NULL): v
377379
'severity' => strtoupper($this->getLogPriorityForHttpCode($http_code)),
378380
self::PROP_INGEN_TYPE => 'rqst',
379381
'httpRequest' => $meta['context']['httpRequest'] ?? [],
382+
'context' => [
383+
'mem_mb' => sprintf('%.2f',\memory_get_peak_usage() / 1_000_000),
384+
],
380385
],
381386
$meta,
382387
[
383388
'httpRequest' => [
384389
// requestMethod, requestUrl, remoteIp are expected to come from metadata provider as they are
385390
// shared with application log entry context
386391
'status' => $http_code,
387-
'userAgent' => $server['HTTP_USER_AGENT'] ?? NULL,
392+
'userAgent' => $this->truncate($server['HTTP_USER_AGENT'] ?? NULL, 500),
388393
'latency' => $this->calculateRequestLatency($request_start_time),
389394
],
390395
]
@@ -395,6 +400,22 @@ public function logRequest(?array $server, ?float $request_start_time = NULL): v
395400
$this->writeLog($log_entry);
396401
}
397402

403+
private function truncate(?string $value, int $max_byte_length): ?string
404+
{
405+
if ($value === NULL) {
406+
return NULL;
407+
}
408+
409+
if (strlen($value) <= $max_byte_length) {
410+
return $value;
411+
}
412+
413+
// Use mb_strcut because we want to limit the byte size but keep a utf-8 valid string:
414+
// * substr might split a utf8 character and cause an encoding error.
415+
// * mb_substr could allow a much longer byte length if the string was entirely composed of unicode characters.
416+
return trim(mb_strcut($value, 0, $max_byte_length)).'';
417+
}
418+
398419
protected function getLogPriorityForHttpCode(string $code): string
399420
{
400421
$map = [

test/unit/Logging/StackdriverApplicationLoggerTest.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -543,10 +543,8 @@ public function test_its_request_logger_logs_custom_metadata()
543543
$logger = $this->newSubject();
544544
$logger->logRequest([]);
545545
$entry = $this->assertLoggedOneLine();
546-
$this->assertSame(
547-
['user' => 'john@do.com'],
548-
$entry['context']
549-
);
546+
$this->assertSame(['mem_mb', 'user'], array_keys($entry['context']));
547+
$this->assertSame('john@do.com', $entry['context']['user']);
550548
}
551549

552550
public function test_its_request_logger_logs_http_response_code()
@@ -571,6 +569,16 @@ public function test_its_request_logger_logs_user_agent_from_global_array($serve
571569
$this->assertSame($expect, $entry['httpRequest']['userAgent']);
572570
}
573571

572+
public function test_its_request_logger_truncates_long_user_agents()
573+
{
574+
$ua_val = 'abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 long ※ note the utf8 safe truncation and trimming';
575+
$expect = 'abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 abcdefghijklmnopqrstuvwxyz 1234567890 long…';
576+
$logger = $this->newSubject();
577+
$logger->logRequest(['HTTP_USER_AGENT' => $ua_val]);
578+
$entry = $this->assertLoggedOneLine();
579+
$this->assertSame($expect, $entry['httpRequest']['userAgent']);
580+
}
581+
574582
public function test_its_request_logger_logs_latency_since_start_time_if_provided()
575583
{
576584
$start = \microtime(TRUE);
@@ -591,6 +599,31 @@ public function test_its_request_logger_logs_null_latency_if_no_start_time()
591599
$this->assertNull($entry['httpRequest']['latency']);
592600
}
593601

602+
public function test_its_request_logger_logs_peak_memory_usage_in_mb()
603+
{
604+
$logger = $this->newSubject();
605+
$peak_before = \memory_get_peak_usage();
606+
$logger->logRequest([]);
607+
$peak_after = \memory_get_peak_usage();
608+
$entry = $this->assertLoggedOneLine();
609+
$this->assertIsString(
610+
$entry['context']['mem_mb'],
611+
'Encode memory as string to avoid risk of JSON serialization precision increasing payload size'
612+
);
613+
$this->assertMatchesRegularExpression('/^\d+\.\d{2}$/', $entry['context']['mem_mb']);
614+
615+
$logged_meg = $entry['context']['mem_mb'];
616+
// Sanity check that it's definitely logging in megabytes. This is tricky as there's no way to stub the actual
617+
// memory usage that the logger saw - but it must be no less than the value before we called it, and no more
618+
// than the value immediately after we called it
619+
$this->assertTrue(
620+
($logged_meg >= round($peak_before / 1000 / 1000, 2))
621+
and
622+
($logged_meg <= round($peak_after / 1000 / 1000, 2)),
623+
"Logged value should be within $peak_before and $peak_after and in the right units"
624+
);
625+
}
626+
594627
/**
595628
* @return array
596629
*/

0 commit comments

Comments
 (0)