Skip to content

Commit 78d85fb

Browse files
[12.x] Introduce ContextLogProcessor (#54851)
* introduce setContextToLogProcessor * rename * tests * tests * tests * add context log processor * use processor * return type * removes unnecessary import * Update ContextTest.php * add interface to contracts * formatting * Update ContextServiceProvider.php --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 6468835 commit 78d85fb

File tree

5 files changed

+127
-11
lines changed

5 files changed

+127
-11
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Illuminate\Contracts\Log;
4+
5+
use Monolog\Processor\ProcessorInterface;
6+
7+
interface ContextLogProcessor extends ProcessorInterface
8+
{
9+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Illuminate\Log\Context;
4+
5+
use Illuminate\Contracts\Foundation\Application;
6+
use Illuminate\Contracts\Log\ContextLogProcessor as ContextLogProcessorContract;
7+
use Illuminate\Log\Context\Repository as ContextRepository;
8+
use Monolog\LogRecord;
9+
10+
class ContextLogProcessor implements ContextLogProcessorContract
11+
{
12+
/**
13+
* Create a new ContextLogProcessor instance.
14+
*
15+
* @param \Illuminate\Contracts\Foundation\Application $app
16+
* @return void
17+
*/
18+
public function __construct(protected Application $app)
19+
{
20+
}
21+
22+
/**
23+
* Add contextual data to the log's "extra" parameter.
24+
*
25+
* @param \Monolog\LogRecord $record
26+
* @return \Monolog\LogRecord
27+
*/
28+
public function __invoke(LogRecord $record): LogRecord
29+
{
30+
if (! $this->app->bound(ContextRepository::class)) {
31+
return $record;
32+
}
33+
34+
return $record->with(extra: [
35+
...$record->extra,
36+
...$this->app[ContextRepository::class]->all(),
37+
]);
38+
}
39+
}

src/Illuminate/Log/Context/ContextServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Log\Context;
44

5+
use Illuminate\Contracts\Log\ContextLogProcessor as ContextLogProcessorContract;
56
use Illuminate\Queue\Events\JobProcessing;
67
use Illuminate\Queue\Queue;
78
use Illuminate\Support\Facades\Context;
@@ -17,6 +18,8 @@ class ContextServiceProvider extends ServiceProvider
1718
public function register()
1819
{
1920
$this->app->scoped(Repository::class);
21+
22+
$this->app->bind(ContextLogProcessorContract::class, fn ($app) => new ContextLogProcessor($app));
2023
}
2124

2225
/**

src/Illuminate/Log/LogManager.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Illuminate\Log;
44

55
use Closure;
6-
use Illuminate\Log\Context\Repository as ContextRepository;
6+
use Illuminate\Contracts\Log\ContextLogProcessor;
77
use Illuminate\Support\Collection;
88
use Illuminate\Support\Str;
99
use InvalidArgumentException;
@@ -143,16 +143,7 @@ protected function get($name, ?array $config = null)
143143
)->withContext($this->sharedContext);
144144

145145
if (method_exists($loggerWithContext->getLogger(), 'pushProcessor')) {
146-
$loggerWithContext->pushProcessor(function ($record) {
147-
if (! $this->app->bound(ContextRepository::class)) {
148-
return $record;
149-
}
150-
151-
return $record->with(extra: [
152-
...$record->extra,
153-
...$this->app[ContextRepository::class]->all(),
154-
]);
155-
});
146+
$loggerWithContext->pushProcessor($this->app->make(ContextLogProcessor::class));
156147
}
157148

158149
return $this->channels[$name] = $loggerWithContext;

tests/Log/ContextTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Exception;
66
use Illuminate\Contracts\Debug\ExceptionHandler;
7+
use Illuminate\Contracts\Log\ContextLogProcessor;
78
use Illuminate\Database\Eloquent\Model;
89
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
910
use Illuminate\Log\Context\Events\ContextDehydrating as Dehydrating;
@@ -13,13 +14,21 @@
1314
use Illuminate\Support\Facades\Event;
1415
use Illuminate\Support\Facades\Log;
1516
use Illuminate\Support\Str;
17+
use Monolog\LogRecord;
1618
use Orchestra\Testbench\TestCase;
1719
use RuntimeException;
1820

1921
class ContextTest extends TestCase
2022
{
2123
use LazilyRefreshDatabase;
2224

25+
#[\Override]
26+
protected function tearDown(): void
27+
{
28+
parent::tearDown();
29+
MyAddContextProcessor::$wasConstructed = false;
30+
}
31+
2332
public function test_it_can_set_values()
2433
{
2534
$values = [
@@ -544,6 +553,55 @@ public function test_scope_sets_keys_and_restores()
544553
'hiddenKey2' => 'world',
545554
], Context::allHidden());
546555
}
556+
557+
public function test_uses_closure_for_context_processor()
558+
{
559+
$path = storage_path('logs/laravel.log');
560+
file_put_contents($path, '');
561+
562+
$this->app->bind(
563+
ContextLogProcessor::class,
564+
fn () => function (LogRecord $record): LogRecord {
565+
$logChannel = Context::getHidden('log_channel_name');
566+
567+
return $record->with(
568+
// allow overriding the context from what's been set on the log
569+
context: array_merge(Context::all(), $record->context),
570+
// use the log channel we've set in context, or fallback to the current channel
571+
channel: $logChannel ?? $record->channel,
572+
);
573+
}
574+
);
575+
576+
Context::addHidden('log_channel_name', 'closure-test');
577+
Context::add(['value_from_context' => 'hello']);
578+
579+
Log::info('This is an info log.', ['value_from_log_info_context' => 'foo']);
580+
581+
$log = Str::after(file_get_contents($path), '] ');
582+
$this->assertSame('closure-test.INFO: This is an info log. {"value_from_context":"hello","value_from_log_info_context":"foo"}', Str::trim($log));
583+
file_put_contents($path, '');
584+
}
585+
586+
public function test_can_rebind_to_separate_class()
587+
{
588+
$path = storage_path('logs/laravel.log');
589+
file_put_contents($path, '');
590+
591+
$this->app->bind(ContextLogProcessor::class, MyAddContextProcessor::class);
592+
593+
Context::add(['this-will-be-included' => false]);
594+
595+
Log::info('This is an info log.', ['value_from_log_info_context' => 'foo']);
596+
$log = Str::after(file_get_contents($path), '] ');
597+
$this->assertSame(
598+
'testing.INFO: This is an info log. {"value_from_log_info_context":"foo","inside of MyAddContextProcessor":true}',
599+
Str::trim($log)
600+
);
601+
$this->assertTrue(MyAddContextProcessor::$wasConstructed);
602+
603+
file_put_contents($path, '');
604+
}
547605
}
548606

549607
enum Suit
@@ -566,3 +624,19 @@ class ContextModel extends Model
566624
{
567625
//
568626
}
627+
628+
class MyAddContextProcessor implements ContextLogProcessor
629+
{
630+
public static bool $wasConstructed = false;
631+
632+
public function __construct()
633+
{
634+
self::$wasConstructed = true;
635+
}
636+
637+
#[\Override]
638+
public function __invoke(LogRecord $record): LogRecord
639+
{
640+
return $record->with(context: array_merge($record->context, ['inside of MyAddContextProcessor' => true]));
641+
}
642+
}

0 commit comments

Comments
 (0)