-
Notifications
You must be signed in to change notification settings - Fork 11.5k
[11.x] Introduce storing log channel name on Context #54692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I personally find this a bit confusing. Going to let @timacdonald take a look. |
@cosmastech, I'm not sure I understand the problem you are trying to solve. Could you provide some more real-world code examples or elaborate a little more? I would love to understand. |
Sure! Say we want to log all actions under one channel name. We start with a web request that calls an action, which in turn calls another service. Here's a code snippet as a for instance<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use App\Models\Team;
use App\Models\School;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class SyncTeamController extends Controller
{
public function __invoke(School $school, SyncTeamsAction $action)
{
Context::add(['school_id' => $school->id]);
Context::setName('sync-school-teams-request');
$action->handle($school);
return response()->noContent();
}
}
class SyncTeamsAction
{
public function __construct(private readonly ExternalTeamsFetcher $teamFetcher) {}
public function handle(School $school)
{
Log::debug("Preparing to sync teams for School {$school->name}. Last synced at: " . $school->last_synced_at);
$teamsToSync = $this->teamFetcher->handle($school->externalId);
foreach($teamsToSync as $team) {
// ... imagine logging some additional details here, like when we mark a team inactive, or create a new team.
}
}
}
class ExternalTeamsFetcher
{
public function handle(string $externalSchoolId): array
{
Log::debug('Fetching team from external API');
$response = Http::get('https://example.com/schools/' . $externalSchoolId . '/teams');
$json = $response->json();
Log::debug('Rate limit remaining: ' . $response->header('X-RateLimit-Remaining'));
return $this->mapResponse($json);
}
private function mapResponse(array $response): array
{
// ...
}
} In order to keep all of these logs in a shared context, we could set a channel name at the outset. Then through each step of the process, we can see them grouped together. Obviously, there are other workarounds for this. (Like adding |
I feel like |
@cosmastech, just for my understanding, is this what it could look like without the feature yet implemented? <?php
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use App\Models\Team;
use App\Models\School;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class SyncTeamController extends Controller
{
public function __invoke(School $school, SyncTeamsAction $action)
{
Context::add(['school_id' => $school->id]);
$action->handle($school);
return response()->noContent();
}
}
class SyncTeamsAction
{
public function __construct(private readonly ExternalTeamsFetcher $teamFetcher) {}
public function handle(School $school)
{
Log::withName('sync-school-teams-request')->debug("Preparing to sync teams for School {$school->name}. Last synced at: " . $school->last_synced_at);
$teamsToSync = $this->teamFetcher->handle($school->externalId);
foreach($teamsToSync as $team) {
// ... imagine logging some additional details here, like when we mark a team inactive, or create a new team.
}
}
}
class ExternalTeamsFetcher
{
public function handle(string $externalSchoolId): array
{
Log::withName('sync-school-teams-request')->debug('Fetching team from external API');
$response = Http::get('https://example.com/schools/' . $externalSchoolId . '/teams');
$json = $response->json();
Log::withName('sync-school-teams-request')->debug('Rate limit remaining: ' . $response->header('X-RateLimit-Remaining'));
return $this->mapResponse($json);
}
private function mapResponse(array $response): array
{
// ...
}
} |
Yep! |
I think we're going to table this one for now. |
The Context facade is nearly perfect. I love that I can carry about contextual information from function to function without having to a pass an instance of a LogInterface or the contextual information as an array.
However, the one missing piece is that I cannot set the channel name via Context. So for instance, I currently have to write code like this:
which will produce logs like this (when written to a log file)
Obviously, within a single function this isn't too bad, but when the information has multiple calls to other services, it can be unwieldy.
Why would you need to use this?
In a large application, observability tools like DataDog make it possible to query just by the channel name. If your application writes thousands of logs per hour, being able to filter down to only the relevant logs is incredibly helpful. This is like adding additional context, except it is standardized for logging platforms.
A solution
Introducing
Context::setName()
. We can pass a string here, and now all of our logs with shared context will be under the same name.Limitations
You cannot override the channel name without modifying the Context. So, if I want to change the channel name for just a single log (
Log::withName('not-user-sync')->info('Doing something else.')
), the name set by the context will override the value specified. I considered some options for this, but didn't really have any clear approach. I thought about maybe storing a reference to channel name on Log\Logger, but not sure how I feel about that.