From 04e7cab313650ed1bd966f149982c06a998c7a87 Mon Sep 17 00:00:00 2001 From: Jens-Uwe Mager Date: Tue, 29 Jul 2025 17:46:44 +0200 Subject: [PATCH 1/3] Add a 'stdlog' log type. The stdlog log type is similar to the errorlog log type, but it does output to stderr and makes sure the output is formatted as JSON. This is in particular useful for environments running in docker containers, with the stderr being collected by log aggregation services like Grafana or Google Cloud logging. --- lib/private/Log/LogFactory.php | 3 +- lib/private/Log/Stdlog.php | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 lib/private/Log/Stdlog.php diff --git a/lib/private/Log/LogFactory.php b/lib/private/Log/LogFactory.php index ee6054b81f8fd..84d7411c4f965 100644 --- a/lib/private/Log/LogFactory.php +++ b/lib/private/Log/LogFactory.php @@ -1,5 +1,4 @@ new Errorlog($this->systemConfig), + 'stdlog' => new Stdlog($this->systemConfig), 'syslog' => $this->c->resolve(Syslog::class), 'systemd' => $this->c->resolve(Systemdlog::class), 'file' => $this->buildLogFile(), @@ -36,6 +36,7 @@ public function get(string $type):IWriter { protected function createNewLogger(string $type, string $tag, string $path): IWriter { return match (strtolower($type)) { 'errorlog' => new Errorlog($this->systemConfig, $tag), + 'stdlog' => new Stdlog($this->systemConfig, $tag), 'syslog' => new Syslog($this->systemConfig, $tag), 'systemd' => new Systemdlog($this->systemConfig, $tag), default => $this->buildLogFile($path), diff --git a/lib/private/Log/Stdlog.php b/lib/private/Log/Stdlog.php new file mode 100644 index 0000000000000..83d7e36797ce9 --- /dev/null +++ b/lib/private/Log/Stdlog.php @@ -0,0 +1,65 @@ +logDetailsAsJSON($app, $message, $level); + $details = json_decode($detailsJson, true); + + if (json_last_error() !== JSON_ERROR_NONE || !is_array($details)) { + return; + } + + $logEntry = array_merge([ + 'tag' => $this->tag, + 'app' => $app, + 'level' => $level, + ], $details); + // Check if 'message' field exists and is a string + if (isset($logEntry['message']) && is_string($logEntry['message'])) { + $msg = $logEntry['message']; + + if (strlen($msg) > 0 && $msg[0] === '{') { + // Try decoding JSON + $decoded = json_decode($msg, true); + + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { + // Remove original 'message' field + unset($logEntry['message']); + + // Flatten decoded JSON into top-level logEntry + // This will overwrite existing keys if there are + // conflicts + $logEntry = array_merge($logEntry, $decoded); + } + } + } + + $json = json_encode($logEntry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + if ($json !== false) { + file_put_contents('php://stderr', $json . PHP_EOL); + } + } +} From 60fe227b66ccecdb7ec86ee6b0d82942f727811a Mon Sep 17 00:00:00 2001 From: Jens-Uwe Mager Date: Mon, 29 Dec 2025 12:58:04 -0500 Subject: [PATCH 2/3] Add traceparent support. --- lib/private/Log/Stdlog.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/private/Log/Stdlog.php b/lib/private/Log/Stdlog.php index 83d7e36797ce9..65493d753a950 100644 --- a/lib/private/Log/Stdlog.php +++ b/lib/private/Log/Stdlog.php @@ -36,6 +36,17 @@ public function write(string $app, $message, int $level): void { 'app' => $app, 'level' => $level, ], $details); + $traceparent = $_SERVER['HTTP_TRACEPARENT']; + if (preg_match('/^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/', $traceparent, $matches)) { + $gcp = getenv('GOOGLE_CLOUD_PROJECT'); + if (!empty($gcp)) { + $logEntry['logging.googleapis.com/trace'] = 'projects/' . $gcp . '/traces/' . $matches[1]; + $logEntry['logging.googleapis.com/spanId'] = $matches[2]; + } else { + $logEntry['traceId'] = $matches[1]; + $logEntry['spanId'] = $matches[2]; + } + } // Check if 'message' field exists and is a string if (isset($logEntry['message']) && is_string($logEntry['message'])) { $msg = $logEntry['message']; From d37edb563aa5ac61f972d01753e0ac4037b9e9af Mon Sep 17 00:00:00 2001 From: Jens-Uwe Mager Date: Fri, 29 May 2026 16:27:18 -0400 Subject: [PATCH 3/3] If there is no trace parent, do nothing. --- lib/private/Log/Stdlog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Log/Stdlog.php b/lib/private/Log/Stdlog.php index 65493d753a950..013881211aa5b 100644 --- a/lib/private/Log/Stdlog.php +++ b/lib/private/Log/Stdlog.php @@ -36,7 +36,7 @@ public function write(string $app, $message, int $level): void { 'app' => $app, 'level' => $level, ], $details); - $traceparent = $_SERVER['HTTP_TRACEPARENT']; + $traceparent = $_SERVER['HTTP_TRACEPARENT'] ?? ''; if (preg_match('/^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/', $traceparent, $matches)) { $gcp = getenv('GOOGLE_CLOUD_PROJECT'); if (!empty($gcp)) {