2424 */
2525final class GoogleCloudLoggingFormatter extends JsonFormatter
2626{
27+ const CONTEXT_HEADER_FORMAT = '/([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?/ ' ;
28+
29+ private static ?string $ traceID = null ;
30+
2731 protected function normalizeRecord (LogRecord $ record ): array
2832 {
2933 $ normalized = parent ::normalizeRecord ($ record );
@@ -32,9 +36,49 @@ protected function normalizeRecord(LogRecord $record): array
3236 $ normalized ['severity ' ] = $ normalized ['level_name ' ];
3337 $ normalized ['time ' ] = $ record ->datetime ->format (DateTimeInterface::RFC3339_EXTENDED );
3438
39+ // Tag with Trace ID for request attribution
40+ $ normalized ['logging.googleapis.com/trace ' ] = $ this ->getTraceID ();
41+
3542 // Remove keys that are not used by GCP
3643 unset($ normalized ['level ' ], $ normalized ['level_name ' ], $ normalized ['datetime ' ]);
3744
3845 return $ normalized ;
3946 }
47+
48+ private function getTraceID (): ?string
49+ {
50+ if (empty ($ this ->traceID ) && !empty ($ _SERVER ['HTTP_X_CLOUD_TRACE_CONTEXT ' ])) {
51+ $ matched = preg_match (
52+ self ::CONTEXT_HEADER_FORMAT ,
53+ $ _SERVER ['HTTP_X_CLOUD_TRACE_CONTEXT ' ] ?? '' ,
54+ $ matches ,
55+ );
56+
57+ if (!$ matched ) {
58+ return null ;
59+ }
60+
61+ $ projectID = $ this ->getProjectID ();
62+ if (empty ($ projectID )) {
63+ return null ;
64+ }
65+
66+ $ this ->traceID = 'projects/ ' .$ projectID .'/traces/ ' .strtolower ($ matches [1 ]);
67+ }
68+
69+ return $ this ->traceID ;
70+ }
71+
72+ private function getProjectID (): ?string
73+ {
74+ if (isset ($ _SERVER ['GOOGLE_CLOUD_PROJECT ' ])) {
75+ return $ _SERVER ['GOOGLE_CLOUD_PROJECT ' ];
76+ }
77+
78+ if (class_exists ('\Google\Cloud\Core\Compute\Metadata ' )) {
79+ return (new \Google \Cloud \Core \Compute \Metadata ())->getProjectId ();
80+ }
81+
82+ return null ;
83+ }
4084}
0 commit comments