Skip to content

컨텍스트

소개

라라벨의 "컨텍스트" 기능은 애플리케이션 내에서 실행되는 요청, 작업 및 명령 전체에서 정보를 캡처, 검색 및 공유할 수 있도록 합니다. 이 캡처된 정보는 애플리케이션에서 작성한 로그에도 포함되어 로그 항목이 작성되기 전에 발생한 주변 코드 실행 기록에 대한 더 깊은 통찰력을 제공하고 분산 시스템 전체에서 실행 흐름을 추적할 수 있도록 합니다.

작동 방식

라라벨의 컨텍스트 기능을 이해하는 가장 좋은 방법은 내장 로깅 기능을 사용하여 실제로 확인하는 것입니다. 시작하려면 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가 포함되지만, 컨텍스트의 urltrace_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);

작업이 디스패치되면 현재 컨텍스트에 저장된 모든 정보가 캡처되어 작업과 공유됩니다. 캡처된 정보는 작업이 실행되는 동안 현재 컨텍스트로 다시 채워집니다. 따라서 작업의 핸들 메서드가 로그에 쓰도록 설정되었다면,

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"

조건부 컨텍스트

when 메서드를 사용하여 주어진 조건에 따라 컨텍스트에 데이터를 추가할 수 있습니다. when 메서드에 제공된 첫 번째 클로저는 주어진 조건이 true로 평가되면 호출되고, 두 번째 클로저는 조건이 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', []),
);

스택

컨텍스트는 추가된 순서대로 저장된 데이터 목록인 "스택"을 생성하는 기능을 제공합니다. push 메서드를 호출하여 스택에 정보를 추가할 수 있습니다.

use Illuminate\Support\Facades\Context;
 
Context::push('breadcrumbs', 'first_value');
 
Context::push('breadcrumbs', 'second_value', 'third_value');
 
Context::get('breadcrumbs');
// [
// 'first_value',
// 'second_value',
// 'third_value',
// ]

스택은 애플리케이션 전체에서 발생하는 이벤트와 같이 요청에 대한 기록 정보를 캡처하는 데 유용할 수 있습니다. 예를 들어 쿼리가 실행될 때마다 스택에 푸시하는 이벤트 리스너를 생성하여 쿼리 SQL 및 지속 시간을 튜플로 캡처할 수 있습니다.

use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;
 
DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});

stackContainshiddenStackContains 메서드를 사용하여 스택에 값이 있는지 확인할 수 있습니다.

if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
 
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}

stackContainshiddenStackContains 메서드는 두 번째 인수로 클로저를 허용하여 값 비교 연산을 보다 세밀하게 제어할 수 있습니다.

use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
 
return Context::stackContains('breadcrumbs', function ($value) {
return Str::startsWith($value, 'query_');
});

컨텍스트 검색

Context 파사드의 get 메서드를 사용하여 컨텍스트에서 정보를 검색할 수 있습니다.

use Illuminate\Support\Facades\Context;
 
$value = Context::get('key');

only 메서드를 사용하여 컨텍스트에서 정보의 하위 집합을 검색할 수 있습니다.

$data = Context::only(['first_key', 'second_key']);

pull 메서드를 사용하여 컨텍스트에서 정보를 검색하고 즉시 컨텍스트에서 제거할 수 있습니다.

$value = Context::pull('key');

컨텍스트 데이터가 스택에 저장된 경우 pop 메서드를 사용하여 스택에서 항목을 꺼낼 수 있습니다.

Context::push('breadcrumbs', 'first_value', 'second_value');
 
Context::pop('breadcrumbs')
// second_value
 
Context::get('breadcrumbs');
// ['first_value']

컨텍스트에 저장된 모든 정보를 검색하려면 all 메서드를 호출하면 됩니다.

$data = Context::all();

항목 존재 여부 확인

컨텍스트에 주어진 키에 대해 저장된 값이 있는지 확인하려면 has 메서드를 사용할 수 있습니다.

use Illuminate\Support\Facades\Context;
 
if (Context::has('key')) {
// ...
}

has 메서드는 저장된 값에 관계없이 true를 반환합니다. 예를 들어, null 값을 가진 키는 존재하는 것으로 간주됩니다.

Context::add('key', null);
 
Context::has('key');
// true

컨텍스트 제거

forget 메서드를 사용하여 현재 컨텍스트에서 키와 해당 값을 제거할 수 있습니다.

use Illuminate\Support\Facades\Context;
 
Context::add(['first_key' => 1, 'second_key' => 2]);
 
Context::forget('first_key');
 
Context::all();
 
// ['second_key' => 2]

forget 메서드에 배열을 제공하여 여러 키를 한 번에 제거할 수 있습니다.

Context::forget(['first_key', 'second_key']);

숨겨진 컨텍스트

컨텍스트는 "숨겨진" 데이터를 저장하는 기능을 제공합니다. 이 숨겨진 정보는 로그에 추가되지 않으며, 위에서 설명한 데이터 검색 메서드를 통해 접근할 수 없습니다. 컨텍스트는 숨겨진 컨텍스트 정보와 상호 작용하기 위한 별도의 메서드 집합을 제공합니다.

use Illuminate\Support\Facades\Context;
 
Context::addHidden('key', 'value');
 
Context::getHidden('key');
// 'value'
 
Context::get('key');
// null

"숨겨진" 메서드는 위에서 설명한 숨겨지지 않은 메서드의 기능을 미러링합니다.

Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::forgetHidden(/* ... */);

이벤트

Context는 컨텍스트의 수화 및 탈수화 프로세스에 연결할 수 있는 두 가지 이벤트를 디스패치합니다.

이러한 이벤트가 어떻게 사용될 수 있는지 설명하기 위해, 애플리케이션의 미들웨어에서 들어오는 HTTP 요청의 Accept-Language 헤더를 기반으로 app.locale 구성 값을 설정한다고 가정해 보겠습니다. 컨텍스트의 이벤트를 사용하면 요청 중에 이 값을 캡처하고 큐에서 복원하여 큐로 전송된 알림이 올바른 app.locale 값을 갖도록 할 수 있습니다. 컨텍스트의 이벤트와 숨김 데이터를 사용하여 이를 달성할 수 있으며, 이는 다음 문서에서 설명합니다.

탈수화

작업이 큐로 디스패치될 때마다 컨텍스트의 데이터는 "탈수화"되어 작업의 페이로드와 함께 캡처됩니다. Context::dehydrating 메서드를 사용하면 탈수화 프로세스 중에 호출될 클로저를 등록할 수 있습니다. 이 클로저 내에서 큐에 있는 작업과 공유될 데이터에 변경 사항을 적용할 수 있습니다.

일반적으로 dehydrating 콜백은 애플리케이션의 AppServiceProvider 클래스의 boot 메서드 내에 등록해야 합니다.

use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;
 
/**
* 모든 애플리케이션 서비스 부트스트랩.
*/
public function boot(): void
{
Context::dehydrating(function (Repository $context) {
$context->addHidden('locale', Config::get('app.locale'));
});
}
lightbulb

dehydrating 콜백 내에서 Context 파사드를 사용하면 현재 프로세스의 컨텍스트가 변경되므로 사용하지 않아야 합니다. 콜백에 전달된 저장소만 변경해야 합니다.

Hydrated

대기열 작업이 대기열에서 실행되기 시작할 때마다 작업과 공유된 모든 컨텍스트가 현재 컨텍스트로 "수화"됩니다. Context::hydrated 메서드를 사용하면 수화 프로세스 중에 호출될 클로저를 등록할 수 있습니다.

일반적으로 애플리케이션의 AppServiceProvider 클래스의 boot 메서드 내에서 hydrated 콜백을 등록해야 합니다.

use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Context::hydrated(function (Repository $context) {
if ($context->hasHidden('locale')) {
Config::set('app.locale', $context->getHidden('locale'));
}
});
}
lightbulb

hydrated 콜백 내에서 Context 파사드를 사용하지 말고 콜백에 전달된 저장소만 변경해야 합니다.