Skip to content

Commit 02f5ef6

Browse files
committed
add ability for global log context setup at PsrLogger
1 parent 2e7a056 commit 02f5ef6

File tree

5 files changed

+176
-67
lines changed

5 files changed

+176
-67
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Yii1 PSR Log extension
22
======================
33

4+
1.0.1 Under Development
5+
-----------------------
6+
7+
- Enh: Added ability for global log context setup at `PsrLogger` (klimov-paul)
8+
9+
410
1.0.0, July 19, 2023
511
--------------------
612

src/AbstractPsrLogger.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use CLogger;
66
use Psr\Log\LoggerInterface;
77
use Psr\Log\LoggerTrait;
8+
use Throwable;
89
use Yii;
910

1011
/**
@@ -37,6 +38,7 @@
3738
abstract class AbstractPsrLogger implements LoggerInterface
3839
{
3940
use LoggerTrait;
41+
use HasGlobalContext;
4042

4143
/**
4244
* @var \CLogger|null Yii logger to write logs into.
@@ -77,6 +79,11 @@ public function setYiiLogger(?CLogger $yiiLogger): self
7779
*/
7880
protected function writeLog($level, $message, array $context = []): void
7981
{
82+
$context = array_merge(
83+
$this->resolveGlobalContext(),
84+
$context
85+
);
86+
8087
$yiiLogger = $this->getYiiLogger();
8188

8289
if (!$yiiLogger instanceof Logger) {
@@ -89,4 +96,14 @@ protected function writeLog($level, $message, array $context = []): void
8996
$context
9097
);
9198
}
99+
100+
/**
101+
* {@inheritdoc}
102+
*/
103+
protected function logGlobalContextResolutionError(Throwable $exception): void
104+
{
105+
$errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage();
106+
107+
$this->getYiiLogger()->log($errorMessage, CLogger::LEVEL_ERROR, 'system.log');
108+
}
92109
}

src/HasGlobalContext.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace yii1tech\psr\log;
4+
5+
use InvalidArgumentException;
6+
use Throwable;
7+
8+
/**
9+
* HasGlobalContext provides global log context setup and resolution ability.
10+
*
11+
* @mixin \CComponent
12+
*
13+
* @property-write \Closure|array|null $globalContext global log context.
14+
*
15+
* @author Paul Klimov <[email protected]>
16+
* @since 1.0.1
17+
*/
18+
trait HasGlobalContext
19+
{
20+
/**
21+
* @var \Closure|array|null log context, which should be applied to each message.
22+
*/
23+
private $_globalContext;
24+
25+
/**
26+
* Sets the log context, which should be applied to each log message.
27+
* You can use a `\Closure` to specify calculated expression for it.
28+
* For example:
29+
*
30+
* ```php
31+
* $logger = (new Logger)
32+
* ->withGlobalContext(function () {
33+
* return [
34+
* 'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
35+
* ];
36+
* });
37+
* ```
38+
*
39+
* @param \Closure|array|null $globalLogContext global log context.
40+
* @return static self reference.
41+
*/
42+
public function withGlobalContext($globalLogContext): self
43+
{
44+
if ($globalLogContext !== null && !is_array($globalLogContext) && !$globalLogContext instanceof \Closure) {
45+
throw new InvalidArgumentException('"' . get_class($this) . '::$globalLogContext" should be either an array or a `\\Closure`');
46+
}
47+
48+
$this->_globalContext = $globalLogContext;
49+
50+
return $this;
51+
}
52+
53+
/**
54+
* Alias of {@see withGlobalContext()}.
55+
* Supports Yii magic property access.
56+
*
57+
* @param \Closure|array|null $globalLogContext global log context.
58+
* @return static self reference.
59+
*/
60+
public function setGlobalContext($globalLogContext): self
61+
{
62+
return $this->withGlobalContext($globalLogContext);
63+
}
64+
65+
/**
66+
* Returns global log context for particular message.
67+
* If global context is set as callable, it will be executed each time.
68+
*
69+
* @return array log context.
70+
*/
71+
protected function resolveGlobalContext(): array
72+
{
73+
if ($this->_globalContext === null) {
74+
return [];
75+
}
76+
77+
if ($this->_globalContext instanceof \Closure) {
78+
try {
79+
return call_user_func($this->_globalContext);
80+
} catch (Throwable $exception) {
81+
$this->logGlobalContextResolutionError($exception);
82+
83+
return [];
84+
}
85+
}
86+
87+
return $this->_globalContext;
88+
}
89+
90+
/**
91+
* Logs the error which occurs at during global context resolution.
92+
*
93+
* @param \Throwable $exception error exception.
94+
* @return void
95+
*/
96+
protected function logGlobalContextResolutionError(Throwable $exception): void
97+
{
98+
syslog(LOG_ERR, 'Unable to resolve global log context: ' . $exception->getMessage());
99+
}
100+
}

src/Logger.php

Lines changed: 20 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
namespace yii1tech\psr\log;
44

55
use CLogger;
6-
use InvalidArgumentException;
76
use Psr\Log\LoggerInterface;
87
use Psr\Log\LogLevel;
8+
use Throwable;
99
use Yii;
1010

1111
/**
@@ -40,6 +40,8 @@
4040
*/
4141
class Logger extends CLogger
4242
{
43+
use HasGlobalContext;
44+
4345
/**
4446
* @var bool whether original Yii logging mechanism should be used or not.
4547
*/
@@ -55,11 +57,6 @@ class Logger extends CLogger
5557
*/
5658
private $_psrLogger;
5759

58-
/**
59-
* @var \Closure|array
60-
*/
61-
private $_globalLogContext;
62-
6360
/**
6461
* @return \Psr\Log\LoggerInterface|null related PSR logger instance.
6562
*/
@@ -92,34 +89,6 @@ public function setPsrLogger($psrLogger): self
9289
return $this;
9390
}
9491

95-
/**
96-
* Sets the log context, which should be applied to each log message.
97-
* You can use a `\Closure` to specify calculated expression for it.
98-
* For example:
99-
*
100-
* ```php
101-
* $logger = \yii1tech\psr\log\Logger::new()
102-
* ->withContext(function () {
103-
* return [
104-
* 'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
105-
* ];
106-
* });
107-
* ```
108-
*
109-
* @param \Closure|array|null $globalLogContext global log context.
110-
* @return static self reference.
111-
*/
112-
public function withGlobalContext($globalLogContext): self
113-
{
114-
if ($globalLogContext !== null && !is_array($globalLogContext) && !$globalLogContext instanceof \Closure) {
115-
throw new InvalidArgumentException('"' . get_class($this) . '::$globalLogContext" should be either an array or a `\\Closure`');
116-
}
117-
118-
$this->_globalLogContext = $globalLogContext;
119-
120-
return $this;
121-
}
122-
12392
/**
12493
* @see $yiiLogEnabled
12594
*
@@ -153,7 +122,7 @@ public function log($message, $level = 'info', $category = 'application'): void
153122
{
154123
if (is_array($category)) {
155124
$rawContext = array_merge(
156-
$this->getGlobalLogContext(),
125+
$this->resolveGlobalContext(),
157126
$category
158127
);
159128
$context = $rawContext;
@@ -165,7 +134,7 @@ public function log($message, $level = 'info', $category = 'application'): void
165134
$context['category'] = $category;
166135
}
167136
} else {
168-
$rawContext = $this->getGlobalLogContext();
137+
$rawContext = $this->resolveGlobalContext();
169138
$context = array_merge(
170139
$rawContext,
171140
[
@@ -192,41 +161,25 @@ public function log($message, $level = 'info', $category = 'application'): void
192161
}
193162

194163
/**
195-
* Returns global log context.
196-
*
197-
* @return array log context.
164+
* {@inheritdoc}
198165
*/
199-
protected function getGlobalLogContext(): array
166+
protected function logGlobalContextResolutionError(Throwable $exception): void
200167
{
201-
if ($this->_globalLogContext === null) {
202-
return [];
203-
}
204-
205-
if ($this->_globalLogContext instanceof \Closure) {
206-
try {
207-
return call_user_func($this->_globalLogContext);
208-
} catch (\Throwable $exception) {
209-
$errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage();
210-
211-
if (($psrLogger = $this->getPsrLogger()) !== null) {
212-
$psrLogger->log(
213-
LogLevel::ERROR,
214-
$errorMessage,
215-
[
216-
'exception' => $exception,
217-
]
218-
);
219-
}
220-
221-
if ($this->yiiLogEnabled) {
222-
parent::log($errorMessage, CLogger::LEVEL_ERROR, 'system.log');
223-
}
168+
$errorMessage = 'Unable to resolve global log context: ' . $exception->getMessage();
224169

225-
return [];
226-
}
170+
if (($psrLogger = $this->getPsrLogger()) !== null) {
171+
$psrLogger->log(
172+
LogLevel::ERROR,
173+
$errorMessage,
174+
[
175+
'exception' => $exception,
176+
]
177+
);
227178
}
228179

229-
return $this->_globalLogContext;
180+
if ($this->yiiLogEnabled) {
181+
parent::log($errorMessage, CLogger::LEVEL_ERROR, 'system.log');
182+
}
230183
}
231184

232185
/**
@@ -276,7 +229,7 @@ protected function formatLogContext(array $logContext, int $nestedLevel = 0): ar
276229

277230
foreach ($logContext as $key => $value) {
278231
if (is_object($value)) {
279-
if ($value instanceof \Throwable) {
232+
if ($value instanceof Throwable) {
280233
$logContext[$key] = [
281234
'class' => get_class($value),
282235
'code' => $value->getCode(),

tests/PsrLoggerTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,37 @@ public function testWriteLog(): void
5555
$this->assertFalse(empty($logs[0]));
5656
$this->assertSame('application', $logs[0][2]);
5757
}
58+
59+
/**
60+
* @depends testWriteLog
61+
*/
62+
public function testGlobalLogContext(): void
63+
{
64+
$yiiLogger = new CLogger();
65+
66+
$logger = (new PsrLogger())
67+
->setYiiLogger($yiiLogger);
68+
69+
$logger->withGlobalContext(function () {
70+
return [
71+
'category' => 'global-category',
72+
];
73+
});
74+
75+
$logger->log('test message', CLogger::LEVEL_INFO);
76+
77+
$logs = $yiiLogger->getLogs();
78+
$yiiLogger->flush();
79+
$this->assertFalse(empty($logs[0]));
80+
$this->assertSame('global-category', $logs[0][2]);
81+
82+
$logger->log('test message', CLogger::LEVEL_INFO, [
83+
'category' => 'test-category',
84+
]);
85+
86+
$logs = $yiiLogger->getLogs();
87+
$yiiLogger->flush();
88+
$this->assertFalse(empty($logs[0]));
89+
$this->assertSame('test-category', $logs[0][2]);
90+
}
5891
}

0 commit comments

Comments
 (0)