Skip to content

Commit

Permalink
add ability for global log context setup at PsrLogger
Browse files Browse the repository at this point in the history
  • Loading branch information
klimov-paul committed Jul 20, 2023
1 parent 2e7a056 commit 02f5ef6
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 67 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
--------------------

Expand Down
17 changes: 17 additions & 0 deletions src/AbstractPsrLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use CLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Throwable;
use Yii;

/**
Expand Down Expand Up @@ -37,6 +38,7 @@
abstract class AbstractPsrLogger implements LoggerInterface
{
use LoggerTrait;
use HasGlobalContext;

/**
* @var \CLogger|null Yii logger to write logs into.
Expand Down Expand Up @@ -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) {
Expand All @@ -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');
}
}
100 changes: 100 additions & 0 deletions src/HasGlobalContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace yii1tech\psr\log;

use InvalidArgumentException;
use Throwable;

/**
* HasGlobalContext provides global log context setup and resolution ability.
*
* @mixin \CComponent
*
* @property-write \Closure|array|null $globalContext global log context.
*
* @author Paul Klimov <[email protected]>
* @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());
}
}
87 changes: 20 additions & 67 deletions src/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
namespace yii1tech\psr\log;

use CLogger;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Throwable;
use Yii;

/**
Expand Down Expand Up @@ -40,6 +40,8 @@
*/
class Logger extends CLogger
{
use HasGlobalContext;

/**
* @var bool whether original Yii logging mechanism should be used or not.
*/
Expand All @@ -55,11 +57,6 @@ class Logger extends CLogger
*/
private $_psrLogger;

/**
* @var \Closure|array
*/
private $_globalLogContext;

/**
* @return \Psr\Log\LoggerInterface|null related PSR logger instance.
*/
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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;
Expand All @@ -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,
[
Expand All @@ -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');
}
}

/**
Expand Down Expand Up @@ -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(),
Expand Down
33 changes: 33 additions & 0 deletions tests/PsrLoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}

0 comments on commit 02f5ef6

Please sign in to comment.