Skip to content

[9.x] Share logging context across channels and stacks - #42276

Merged
taylorotwell merged 3 commits into
laravel:9.xfrom
timacdonald:share-context
May 9, 2022
Merged

taylorotwell merged 3 commits into
laravel:9.xfrom
timacdonald:share-context

Conversation

@timacdonald

@timacdonald timacdonald commented May 6, 2022 •

Copy link
Copy Markdown
Member

Purpose

This PR bring the ability to share context across all channels and stacks, rather than just one.

Why this feature is useful

Currently when we specify context, it is on a per log or per channel basis.

// per log...

Log::info('per log', ['log' => 'context']);

// per channel...

Log::withContext(['channel' => 'context']);

Log::channel('stderr')->withContext(['channel' => 'context']);

but there is no way to specify context to be shared across all channels and stacks.

This feature is useful in the example currently shown in the docs for the withContext feature of a channel.

The example in the docs being...

class AssignRequestId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $requestId = (string) Str::uuid();
 
        Log::withContext([
            'request-id' => $requestId
        ]);
 
        return $next($request)->header('Request-Id', $requestId);
    }
}

The problem with this is that it only applies the context the the default channel, but it would be useful if the context was shared across ALL channels and stacks.

Usage

This new feature could be used in a middleware as above, but also in a service provider to apply a unique ID to all invocations of the app, be it from a request or a job or an artisan command.

class AppServiceProvider
{
    public function register()
    {
        //
    }


    public function boot()
    {
        Log::shareContext([
            'invocation-id' => (string) Str::uuid(),
        ]);
    }
}

Now in my application, no matter what logger I use, I'll have the invocation ID in my context.

Log::info(/* ... */);

Log::channel('stderr')->info(/* ... */);

Log::channel('bugsnag')->info(/* ... */);

Other logging systems outside of the Laravel logging system can also hook in and get the shared context to utilise...

$sharedContext = Log::sharedContext();

Notes

  • Not doing anything to the emergency logger. One I couldn't test it, and two if there is something funky happening with the context that is causing the initial logger to fail creation I wouldn't want that to also apply to the emergency logger.
  • Stacks get the shared context at the time they are created, but do not get any additional shared context once resolved - where as channels will always have the latest shared context.
  • Merging is performed in the same manner as withContext i.e. not recursive.

@ankurk91

ankurk91 commented May 6, 2022 •

Copy link
Copy Markdown
Contributor

How can we reset this behavior?
How does it work with Octane?

#37847

@timacdonald

timacdonald commented May 6, 2022 •

Copy link
Copy Markdown
Member Author

Regarding Octane, you would need to hook into events in the ServiceProvider::boot() method...

$this->app['events']->listen([
    RequestReceived::class,
    TaskReceived::class,
    TickReceived::class,
], fn () => Log::shareContext(['invocation-id' => (string) Str::uuid()]));

We could potentially add some magic into Octane to handle this for you, document it on the Octane documentation, or allow the logger to know about Octane and handle this internally.

I didn't add the ability to reset the shared context as I don't have a use-case for that and didn't want to add the feature without one.

The service provider is also just an example. It would continue to be possible to register a "request-id" in a middleware with this new feature.

@taylorotwell

taylorotwell commented May 6, 2022 •

Copy link
Copy Markdown
Member

@timacdonald we would likely need a corresponding PR in Octane to reset the shared context back to its pre-request state after each operation (see other listeners in Octane as examples of this). Marking as draft until we have that worked up as well. 👍

@taylorotwell
taylorotwell marked this pull request as draft May 6, 2022 13:33
@timacdonald

timacdonald commented May 7, 2022 •

Copy link
Copy Markdown
Member Author

@taylorotwell see: 3e5e28e && laravel/octane#513

note that the new test will fail until this PR is merged / tagged. I've tested locally and got the ✅

@timacdonald
timacdonald marked this pull request as ready for review May 7, 2022 09:45
@taylorotwell
taylorotwell merged commit 864ef6a into laravel:9.x May 9, 2022
@timacdonald
timacdonald deleted the share-context branch May 9, 2022 23:37
@omitobi

omitobi commented Dec 18, 2023 •

Copy link
Copy Markdown

I just tested the shareContext() but the logs on my Job does not have the context.
Is this by design or there are other configuration needed?

PS: I tested this on Laravel 10.

salmanarshad321 pushed a commit to salmanarshad321/framework that referenced this pull request Sep 23, 2026
* allow context to be shared across channels and stacks

* add ability to flush the shared context

* formatting
"

Co-authored-by: Taylor Otwell <taylor@laravel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants