diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f967e0..b9f24f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Yii1 PSR Log extension ====================== +1.0.1 Under Development +----------------------- + +- Enh: Added ability for global log context setup at `PsrLogger` (klimov-paul) + + 1.0.0, July 19, 2023 -------------------- diff --git a/src/AbstractPsrLogger.php b/src/AbstractPsrLogger.php index 912948f..ff2a4e2 100644 --- a/src/AbstractPsrLogger.php +++ b/src/AbstractPsrLogger.php @@ -5,6 +5,7 @@ use CLogger; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; +use Throwable; use Yii; /** @@ -37,6 +38,7 @@ abstract class AbstractPsrLogger implements LoggerInterface { use LoggerTrait; + use HasGlobalContext; /** * @var \CLogger|null Yii logger to write logs into. @@ -77,6 +79,11 @@ public function setYiiLogger(?CLogger $yiiLogger): self */ protected function writeLog($level, $message, array $context = []): void { + $context = array_merge( + $this->resolveGlobalContext(), + $context + ); + $yiiLogger = $this->getYiiLogger(); if (!$yiiLogger instanceof Logger) { @@ -89,4 +96,14 @@ protected function writeLog($level, $message, array $context = []): void $context ); } + + /** + * {@inheritdoc} + */ + protected function logGlobalContextResolutionError(Throwable $exception): void + { + $errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage(); + + $this->getYiiLogger()->log($errorMessage, CLogger::LEVEL_ERROR, 'system.log'); + } } \ No newline at end of file diff --git a/src/HasGlobalContext.php b/src/HasGlobalContext.php new file mode 100644 index 0000000..98014f8 --- /dev/null +++ b/src/HasGlobalContext.php @@ -0,0 +1,100 @@ + + * @since 1.0.1 + */ +trait HasGlobalContext +{ + /** + * @var \Closure|array|null log context, which should be applied to each message. + */ + private $_globalContext; + + /** + * Sets the log context, which should be applied to each log message. + * You can use a `\Closure` to specify calculated expression for it. + * For example: + * + * ```php + * $logger = (new Logger) + * ->withGlobalContext(function () { + * return [ + * 'ip' => $_SERVER['REMOTE_ADDR'] ?? null, + * ]; + * }); + * ``` + * + * @param \Closure|array|null $globalLogContext global log context. + * @return static self reference. + */ + public function withGlobalContext($globalLogContext): self + { + if ($globalLogContext !== null && !is_array($globalLogContext) && !$globalLogContext instanceof \Closure) { + throw new InvalidArgumentException('"' . get_class($this) . '::$globalLogContext" should be either an array or a `\\Closure`'); + } + + $this->_globalContext = $globalLogContext; + + return $this; + } + + /** + * Alias of {@see withGlobalContext()}. + * Supports Yii magic property access. + * + * @param \Closure|array|null $globalLogContext global log context. + * @return static self reference. + */ + public function setGlobalContext($globalLogContext): self + { + return $this->withGlobalContext($globalLogContext); + } + + /** + * Returns global log context for particular message. + * If global context is set as callable, it will be executed each time. + * + * @return array log context. + */ + protected function resolveGlobalContext(): array + { + if ($this->_globalContext === null) { + return []; + } + + if ($this->_globalContext instanceof \Closure) { + try { + return call_user_func($this->_globalContext); + } catch (Throwable $exception) { + $this->logGlobalContextResolutionError($exception); + + return []; + } + } + + return $this->_globalContext; + } + + /** + * Logs the error which occurs at during global context resolution. + * + * @param \Throwable $exception error exception. + * @return void + */ + protected function logGlobalContextResolutionError(Throwable $exception): void + { + syslog(LOG_ERR, 'Unable to resolve global log context: ' . $exception->getMessage()); + } +} \ No newline at end of file diff --git a/src/Logger.php b/src/Logger.php index 1e2a46a..5ed2cdf 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -3,9 +3,9 @@ namespace yii1tech\psr\log; use CLogger; -use InvalidArgumentException; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use Throwable; use Yii; /** @@ -40,6 +40,8 @@ */ class Logger extends CLogger { + use HasGlobalContext; + /** * @var bool whether original Yii logging mechanism should be used or not. */ @@ -55,11 +57,6 @@ class Logger extends CLogger */ private $_psrLogger; - /** - * @var \Closure|array - */ - private $_globalLogContext; - /** * @return \Psr\Log\LoggerInterface|null related PSR logger instance. */ @@ -92,34 +89,6 @@ public function setPsrLogger($psrLogger): self return $this; } - /** - * Sets the log context, which should be applied to each log message. - * You can use a `\Closure` to specify calculated expression for it. - * For example: - * - * ```php - * $logger = \yii1tech\psr\log\Logger::new() - * ->withContext(function () { - * return [ - * 'ip' => $_SERVER['REMOTE_ADDR'] ?? null, - * ]; - * }); - * ``` - * - * @param \Closure|array|null $globalLogContext global log context. - * @return static self reference. - */ - public function withGlobalContext($globalLogContext): self - { - if ($globalLogContext !== null && !is_array($globalLogContext) && !$globalLogContext instanceof \Closure) { - throw new InvalidArgumentException('"' . get_class($this) . '::$globalLogContext" should be either an array or a `\\Closure`'); - } - - $this->_globalLogContext = $globalLogContext; - - return $this; - } - /** * @see $yiiLogEnabled * @@ -153,7 +122,7 @@ public function log($message, $level = 'info', $category = 'application'): void { if (is_array($category)) { $rawContext = array_merge( - $this->getGlobalLogContext(), + $this->resolveGlobalContext(), $category ); $context = $rawContext; @@ -165,7 +134,7 @@ public function log($message, $level = 'info', $category = 'application'): void $context['category'] = $category; } } else { - $rawContext = $this->getGlobalLogContext(); + $rawContext = $this->resolveGlobalContext(); $context = array_merge( $rawContext, [ @@ -192,41 +161,25 @@ public function log($message, $level = 'info', $category = 'application'): void } /** - * Returns global log context. - * - * @return array log context. + * {@inheritdoc} */ - protected function getGlobalLogContext(): array + protected function logGlobalContextResolutionError(Throwable $exception): void { - if ($this->_globalLogContext === null) { - return []; - } - - if ($this->_globalLogContext instanceof \Closure) { - try { - return call_user_func($this->_globalLogContext); - } catch (\Throwable $exception) { - $errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage(); - - if (($psrLogger = $this->getPsrLogger()) !== null) { - $psrLogger->log( - LogLevel::ERROR, - $errorMessage, - [ - 'exception' => $exception, - ] - ); - } - - if ($this->yiiLogEnabled) { - parent::log($errorMessage, CLogger::LEVEL_ERROR, 'system.log'); - } + $errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage(); - return []; - } + if (($psrLogger = $this->getPsrLogger()) !== null) { + $psrLogger->log( + LogLevel::ERROR, + $errorMessage, + [ + 'exception' => $exception, + ] + ); } - return $this->_globalLogContext; + if ($this->yiiLogEnabled) { + parent::log($errorMessage, CLogger::LEVEL_ERROR, 'system.log'); + } } /** @@ -276,7 +229,7 @@ protected function formatLogContext(array $logContext, int $nestedLevel = 0): ar foreach ($logContext as $key => $value) { if (is_object($value)) { - if ($value instanceof \Throwable) { + if ($value instanceof Throwable) { $logContext[$key] = [ 'class' => get_class($value), 'code' => $value->getCode(), diff --git a/tests/PsrLoggerTest.php b/tests/PsrLoggerTest.php index be7a78a..4d4c5d1 100644 --- a/tests/PsrLoggerTest.php +++ b/tests/PsrLoggerTest.php @@ -55,4 +55,37 @@ public function testWriteLog(): void $this->assertFalse(empty($logs[0])); $this->assertSame('application', $logs[0][2]); } + + /** + * @depends testWriteLog + */ + public function testGlobalLogContext(): void + { + $yiiLogger = new CLogger(); + + $logger = (new PsrLogger()) + ->setYiiLogger($yiiLogger); + + $logger->withGlobalContext(function () { + return [ + 'category' => 'global-category', + ]; + }); + + $logger->log('test message', CLogger::LEVEL_INFO); + + $logs = $yiiLogger->getLogs(); + $yiiLogger->flush(); + $this->assertFalse(empty($logs[0])); + $this->assertSame('global-category', $logs[0][2]); + + $logger->log('test message', CLogger::LEVEL_INFO, [ + 'category' => 'test-category', + ]); + + $logs = $yiiLogger->getLogs(); + $yiiLogger->flush(); + $this->assertFalse(empty($logs[0])); + $this->assertSame('test-category', $logs[0][2]); + } } \ No newline at end of file