上下文
简介
Laravel 的「上下文」功能使你能够在应用程序中执行的请求、任务和命令中捕获、检索和共享信息。这些捕获的信息也会包含在应用程序编写的日志中,让你更深入地了解编写日志条目之前发生的周围代码执行历史,并允许你在整个分布式系统中跟踪执行流程。
工作原理
了解 Laravel 上下文功能的最佳方式是使用内置的日志功能来查看它的实际应用。首先,你可以使用 Context 门面向上下文添加信息。在这个例子中,我们将使用中间件在每个传入请求中向上下文添加请求 URL 和唯一的跟踪 ID:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AddContext
{
/**
* 处理传入请求。
*/
public function handle(Request $request, Closure $next): Response
{
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
return $next($request);
}
}添加到上下文的信息会自动作为元数据附加到整个请求中编写的任何日志条目。将上下文附加为元数据允许区分传递给单个日志条目的信息与通过 Context 共享的信息。例如,假设我们编写以下日志条目:
Log::info('User authenticated.', ['auth_id' => Auth::id()]);编写的日志将包含传递给日志条目的 auth_id,但也会包含上下文的 url 和 trace_id 作为元数据:
User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}添加到上下文的信息也可用于分派到队列的任务。例如,假设我们在添加一些信息到上下文后将 ProcessPodcast 任务分派到队列:
// 在我们的中间件中...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
// 在我们的控制器中...
ProcessPodcast::dispatch($podcast);当任务被分派时,当前存储在上下文中的任何信息都会被捕获并与任务共享。然后,在任务执行期间,捕获的信息会重新注入到当前上下文中。因此,如果我们的任务的 handle 方法要写入日志:
class ProcessPodcast implements ShouldQueue
{
use Queueable;
// ...
/**
* 执行任务。
*/
public function handle(): void
{
Log::info('Processing podcast.', [
'podcast_id' => $this->podcast->id,
]);
// ...
}
}生成的日志条目将包含在最初分派任务的请求期间添加到上下文的信息:
Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}虽然我们专注于 Laravel 上下文的内置日志相关功能,但以下文档将说明上下文如何允许你在 HTTP 请求/队列任务边界之间共享信息,甚至如何添加不与日志条目一起编写的隐藏上下文数据。
捕获上下文
你可以使用 Context 门面的 add 方法在当前上下文中存储信息:
use Illuminate\Support\Facades\Context;
Context::add('key', 'value');要一次添加多个项目,可以向 add 方法传递关联数组:
Context::add([
'first_key' => 'value',
'second_key' => 'value',
]);add 方法将覆盖任何共享相同键的现有值。如果你只想在键不存在时向上下文添加信息,可以使用 addIf 方法:
Context::add('key', 'first');
Context::get('key');
// "first"
Context::addIf('key', 'second');
Context::get('key');
// "first"上下文还提供了方便的方法来递增或递减给定的键。这两个方法都接受至少一个参数:要跟踪的键。可以提供第二个参数来指定键应该递增或递减的数量:
Context::increment('records_added');
Context::increment('records_added', 5);
Context::decrement('records_added');
Context::decrement('records_added', 5);条件上下文
when 方法可用于根据给定条件向上下文添加数据。如果给定条件评估为 true,则调用提供给 when 方法的第一个闭包,而如果条件评估为 false,则调用第二个闭包:
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Context;
Context::when(
Auth::user()->isAdmin(),
fn ($context) => $context->add('permissions', Auth::user()->permissions),
fn ($context) => $context->add('permissions', []),
);作用域上下文
scope 方法提供了一种在执行给定回调期间临时修改上下文并在回调完成执行后将上下文恢复到其原始状态的方法。此外,你可以传递在闭包执行期间应该合并到上下文中的额外数据(作为第二和第三个参数)。
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Log;
Context::add('trace_id', 'abc-999');
Context::addHidden('user_id', 123);
Context::scope(
function () {
Context::add('action', 'adding_friend');
$userId = Context::getHidden('user_id');
Log::debug("Adding user [{$userId}] to friends list.");
// Adding user [987] to friends list. {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"}
},
data: ['user_name' => 'taylor_otwell'],
hidden: ['user_id' => 987],
);
Context::all();
// [
// 'trace_id' => 'abc-999',
// ]
Context::allHidden();
// [
// 'user_id' => 123,
// ]