1515use GT \WebEngine \Debug \OutputBuffer ;
1616use GT \WebEngine \Debug \Timer ;
1717use GT \WebEngine \Debug \SentryReporter ;
18+ use GT \WebEngine \Debug \SentryLogHandler ;
19+ use GT \WebEngine \Debug \LoggerConfigurationException ;
1820use GT \WebEngine \Redirection \Redirect ;
1921use GT \WebEngine \Redirection \RedirectUri ;
2022use GT \WebEngine \Dispatch \Dispatcher ;
@@ -59,6 +61,7 @@ class Application {
5961 private static bool $ loggerConfigured = false ;
6062 private bool $ finished = false ;
6163 private ?SentryReporter $ sentryReporter = null ;
64+ private ?SentryLogHandler $ sentryLogHandler = null ;
6265
6366 /**
6467 * @param null|array<string, array<string, string>> $globals
@@ -99,6 +102,9 @@ public function __construct(
99102 ], $ globals ?? $ GLOBALS );
100103 $ this ->globalProtection = $ globalProtection ?? new Protection ();
101104 register_shutdown_function ($ handleShutdown ?? $ this ->handleShutdown (...));
105+ if ($ this ->sentryLogHandler ) {
106+ register_shutdown_function ($ this ->sentryLogHandler ->flush (...));
107+ }
102108 }
103109
104110 public function start ():void {
@@ -138,7 +144,7 @@ public function start():void {
138144
139145// Initialise SDK options before protecting globals. Request context is injected
140146// into the reporter, so reporting itself does not require global access.
141- $ this ->sentryReporter ??= SentryReporter:: create ( $ this -> config , $ request );
147+ $ this ->initializeSentry ( );
142148 $ this ->protectGlobals ();
143149
144150// The Dispatcher is a core component responsible for:
@@ -263,6 +269,7 @@ private function finish(
263269
264270 $ this ->timer ->stop ();
265271 $ this ->timer ->logDelta ();
272+ $ this ->sentryLogHandler ?->flush();
266273 }
267274
268275 private function protectGlobals ():void {
@@ -312,22 +319,11 @@ private function loadConfig():Config {
312319 }
313320
314321 private function configureLoggerStreams ():void {
322+ $ destinations = $ this ->getLoggerDestinations ();
315323 if (self ::$ loggerConfigured ) {
316324 return ;
317325 }
318326
319- $ minimumLogLevel = $ this ->getMinimumLogLevel ();
320- $ minimumLogLevelIndex = array_search ($ minimumLogLevel , LogLevel::ALL_LEVELS , true );
321- if ($ minimumLogLevelIndex === false ) {
322- return ;
323- }
324-
325- $ stderrMinLevel = $ this ->getStderrMinimumLogLevel ();
326- $ stderrMinLevelIndex = array_search ($ stderrMinLevel , LogLevel::ALL_LEVELS , true );
327- if ($ stderrMinLevelIndex === false ) {
328- return ;
329- }
330-
331327 if (!class_exists (StdErrHandler::class)) {
332328 return ;
333329 }
@@ -338,7 +334,25 @@ private function configureLoggerStreams():void {
338334 return ;
339335 }
340336
341- LogConfig::setDefaultHandlerLevel ($ minimumLogLevel );
337+ LogConfig::setDefaultHandlerLevel ($ this ->getMinimumLogLevel ());
338+ foreach ($ destinations as $ destination => $ level ) {
339+ if ($ destination === "sentry " ) {
340+ $ this ->sentryLogHandler = new SentryLogHandler ();
341+ LogConfig::addHandler ($ this ->sentryLogHandler , $ level );
342+ }
343+ else {
344+ $ this ->configureLocalLogger ($ level );
345+ }
346+ }
347+ self ::$ loggerConfigured = true ;
348+ }
349+
350+ private function configureLocalLogger (string $ minimumLogLevel ):void {
351+ $ minimumLogLevelIndex = array_search ($ minimumLogLevel , LogLevel::ALL_LEVELS , true );
352+ $ stderrMinLevelIndex = array_search ($ this ->getStderrMinimumLogLevel (), LogLevel::ALL_LEVELS , true );
353+ if ($ minimumLogLevelIndex === false || $ stderrMinLevelIndex === false ) {
354+ return ;
355+ }
342356
343357 if ($ stderrMinLevelIndex > $ minimumLogLevelIndex ) {
344358 $ stdoutMaxLevel = LogLevel::ALL_LEVELS [$ stderrMinLevelIndex - 1 ];
@@ -353,7 +367,13 @@ private function configureLoggerStreams():void {
353367 LogLevel::ALL_LEVELS [max ($ stderrMinLevelIndex , $ minimumLogLevelIndex )],
354368 LogLevel::EMERGENCY ,
355369 );
356- self ::$ loggerConfigured = true ;
370+ }
371+
372+ private function initializeSentry ():void {
373+ $ this ->sentryReporter ??= SentryReporter::create ($ this ->config , $ this ->request );
374+ if ($ this ->sentryLogHandler ) {
375+ $ this ->sentryReporter ?->connectLogHandler($ this ->sentryLogHandler );
376+ }
357377 }
358378
359379 private function handleShutdown ():void {
@@ -456,9 +476,9 @@ private function logErrorMessage(string $message, array $context = []):void {
456476 }
457477
458478 private function getStderrMinimumLogLevel ():string {
459- $ configuredLevel = strtoupper (
479+ $ configuredLevel = strtoupper (trim (
460480 $ this ->config ->getString ("logger.stderr_level " ) ?: LogLevel::ERROR
461- );
481+ )) ;
462482 if (in_array ($ configuredLevel , LogLevel::ALL_LEVELS , true )) {
463483 return $ configuredLevel ;
464484 }
@@ -467,16 +487,41 @@ private function getStderrMinimumLogLevel():string {
467487 }
468488
469489 private function getMinimumLogLevel ():string {
470- $ configuredLevel = $ this ->config -> getString ( " logger.level " )
471- ?: LogLevel::DEBUG ;
472- $ configuredLevel = strtoupper ( $ configuredLevel );
473- if ( in_array ( $ configuredLevel , LogLevel:: ALL_LEVELS , true )) {
474- return $ configuredLevel ;
490+ $ levels = $ this ->getLoggerDestinations ();
491+ foreach ( LogLevel::ALL_LEVELS as $ level ) {
492+ if ( in_array ( $ level , $ levels , true )) {
493+ return $ level ;
494+ }
475495 }
476-
477496 return LogLevel::DEBUG ;
478497 }
479498
499+ /** @return array<string, string> */
500+ private function getLoggerDestinations ():array {
501+ $ types = array_map ("trim " , explode (", " , strtolower ($ this ->config ->getString ("logger.type " ) ?: "stdout " )));
502+ $ levels = array_map ("trim " , explode (", " , strtoupper ($ this ->config ->getString ("logger.level " ) ?: "debug " )));
503+ if (count ($ levels ) !== 1 && count ($ levels ) !== count ($ types )) {
504+ throw new LoggerConfigurationException (
505+ "logger.level must contain one shared level or one level per logger.type destination; "
506+ . count ($ levels ) . " levels supplied for " . count ($ types ) . " destinations. "
507+ );
508+ }
509+ $ destinations = [];
510+ foreach ($ types as $ index => $ type ) {
511+ $ level = $ levels [count ($ levels ) === 1 ? 0 : $ index ];
512+ if (!in_array ($ level , LogLevel::ALL_LEVELS , true )) {
513+ $ level = LogLevel::DEBUG ;
514+ }
515+ // A repeated destination needs only one handler, at its lowest threshold.
516+ if (isset ($ destinations [$ type ]) && array_search ($ destinations [$ type ], LogLevel::ALL_LEVELS , true )
517+ < array_search ($ level , LogLevel::ALL_LEVELS , true )) {
518+ continue ;
519+ }
520+ $ destinations [$ type ] = $ level ;
521+ }
522+ return $ destinations ;
523+ }
524+
480525 /**
481526 * @return array<string, mixed>
482527 * @SuppressWarnings("PHPMD.Superglobals")
0 commit comments