컨텍스트
번역일: 2026년 6월 20일
컨텍스트
소개
Laravel의 컨텍스트(Context) 기능을 사용하면 요청, Job, 커맨드 실행 전반에 걸쳐 정보를 저장하고, 조회하며, 공유할 수 있습니다. 저장된 정보는 애플리케이션이 기록하는 로그에도 자동으로 포함되므로, 특정 로그 항목이 작성되기까지 어떤 코드 흐름이 있었는지 파악하기 쉬워집니다. 분산 시스템에서 요청을 추적할 때도 특히 유용합니다.
동작 원리
컨텍스트 기능을 가장 빠르게 이해하는 방법은 로깅과 함께 실제로 사용해보는 것입니다. Context 파사드를 이용해 컨텍스트에 정보를 추가한 뒤, 로그에 어떻게 반영되는지 살펴보겠습니다.
아래 예제에서는 미들웨어를 사용해 모든 요청마다 요청 URL과 고유한 추적 ID(trace 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('사용자가 인증되었습니다.', ['auth_id' => Auth::id()]);실제로 기록되는 로그에는 auth_id뿐 아니라 컨텍스트에 저장해둔 url과 trace_id도 함께 포함됩니다.
사용자가 인증되었습니다. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}컨텍스트에 저장된 정보는 큐에 디스패치되는 Job에도 전달됩니다. 예를 들어 컨텍스트에 정보를 추가한 뒤 ProcessPodcast Job을 큐에 디스패치한다고 가정해봅시다.
// 미들웨어에서...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
// 컨트롤러에서...
ProcessPodcast::dispatch($podcast);Job이 디스패치될 때 현재 컨텍스트에 저장된 모든 정보가 Job의 페이로드와 함께 캡처됩니다. Job이 실행될 때는 이 정보가 다시 컨텍스트로 복원(hydrate)됩니다. 따라서 Job의 handle 메서드에서 로그를 기록하면:
class ProcessPodcast implements ShouldQueue
{
use Queueable;
// ...
/**
* Job을 실행합니다.
*/
public function handle(): void
{
Log::info('팟캐스트를 처리 중입니다.', [
'podcast_id' => $this->podcast->id,
]);
// ...
}
}기록되는 로그에는 원래 요청에서 저장했던 컨텍스트 정보가 그대로 포함됩니다.
팟캐스트를 처리 중입니다. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}NOTE
컨텍스트는 HTTP 요청과 큐 Job 사이의 경계를 넘어 정보를 공유할 수 있습니다. 또한 로그에는 기록되지 않는 숨김 컨텍스트도 지원합니다.
아래 흐름도는 요청에서 Job까지 컨텍스트가 어떻게 전파되는지 보여줍니다.
컨텍스트 저장
Context 파사드의 add 메서드로 현재 컨텍스트에 정보를 저장할 수 있습니다.
use Illuminate\Support\Facades\Context;
Context::add('key', 'value');여러 항목을 한 번에 추가하려면 연관 배열을 전달하세요.
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"컨텍스트는 숫자 값을 증가/감소시키는 편의 메서드도 제공합니다. 두 번째 인수로 증감량을 지정할 수 있으며, 생략하면 1씩 변경됩니다.
Context::increment('records_added');
Context::increment('records_added', 5);
Context::decrement('records_added');
Context::decrement('records_added', 5);조건부 컨텍스트 저장
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', []),
);스코프 컨텍스트
scope 메서드를 사용하면 특정 클로저가 실행되는 동안만 컨텍스트를 임시로 변경하고, 실행이 끝나면 원래 상태로 자동 복원할 수 있습니다. 두 번째(data)와 세 번째(hidden) 인수로 클로저 실행 중에만 적용할 추가 데이터를 지정할 수 있습니다.
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("사용자 [{$userId}]를 친구 목록에 추가 중입니다.");
// 사용자 [987]를 친구 목록에 추가 중입니다. {"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,
// ]WARNING
스코프 클로저 내부에서 컨텍스트에 저장된 객체를 수정하면, 그 변경 사항은 스코프 밖에서도 반영됩니다. 기본 타입(문자열, 숫자 등)은 스코프 종료 시 원래 값으로 복원되지만, 객체의 내부 상태 변경은 복원되지 않으니 주의하세요.
스택
컨텍스트는 데이터를 추가된 순서대로 보관하는 스택(stack) 구조도 지원합니다. 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',
// ]스택은 요청 처리 중 발생한 이벤트의 이력을 시간 순서대로 기록할 때 유용합니다. 예를 들어 DB 쿼리가 실행될 때마다 SQL과 실행 시간을 스택에 쌓아둘 수 있습니다.
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;
// AppServiceProvider.php 에서...
DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});스택에 특정 값이 포함되어 있는지 확인하려면 stackContains 또는 hiddenStackContains 메서드를 사용합니다.
if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}두 번째 인수에 클로저를 전달하면 보다 세밀한 비교 로직을 적용할 수 있습니다.
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와 except 메서드를 사용하면 컨텍스트의 일부 항목만 선택하거나 제외하여 가져올 수 있습니다.
$data = Context::only(['first_key', 'second_key']);
$data = Context::except(['first_key']);pull 메서드는 값을 조회함과 동시에 컨텍스트에서 해당 항목을 삭제합니다.
$value = Context::pull('key');스택에 저장된 데이터는 pop 메서드로 마지막 값을 꺼낼 수 있습니다(후입선출, LIFO).
Context::push('breadcrumbs', 'first_value', 'second_value');
Context::pop('breadcrumbs');
// second_value
Context::get('breadcrumbs');
// ['first_value']remember와 rememberHidden 메서드는 키가 존재하면 그 값을 반환하고, 존재하지 않으면 클로저를 실행해 반환값을 컨텍스트에 저장한 후 반환합니다.
$permissions = Context::remember(
'user-permissions',
fn () => $user->permissions,
);컨텍스트에 저장된 모든 정보를 가져오려면 all 메서드를 사용하세요.
$data = Context::all();키 존재 확인
has와 missing 메서드로 컨텍스트에 해당 키가 존재하는지 확인할 수 있습니다.
use Illuminate\Support\Facades\Context;
if (Context::has('key')) {
// ...
}
if (Context::missing('key')) {
// ...
}has 메서드는 값이 null이더라도 키가 존재하면 true를 반환합니다.
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]배열을 전달하면 여러 키를 한 번에 삭제할 수 있습니다.
Context::forget(['first_key', 'second_key']);숨김 컨텍스트
컨텍스트는 숨김(hidden) 데이터를 저장하는 기능도 제공합니다. 숨김 데이터는 로그에 기록되지 않으며, 일반 컨텍스트 조회 메서드(get, all 등)로도 접근할 수 없습니다. 숨김 컨텍스트는 별도의 메서드 집합을 통해서만 접근 가능합니다.
use Illuminate\Support\Facades\Context;
Context::addHidden('key', 'value');
Context::getHidden('key');
// 'value'
Context::get('key');
// null숨김 컨텍스트용 메서드는 일반 컨텍스트 메서드와 동일한 방식으로 동작합니다. Hidden이 붙은 메서드들은 숨김 데이터를 대상으로 합니다.
Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::exceptHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::missingHidden(/* ... */);
Context::forgetHidden(/* ... */);NOTE
숨김 컨텍스트는 민감한 정보(인증 토큰, 사용자 ID 등)를 큐 Job 간에 전달해야 하지만 로그에는 노출하고 싶지 않을 때 유용합니다.
이벤트
컨텍스트는 두 가지 이벤트를 제공합니다. 이를 통해 컨텍스트의 탈수(dehydration) 와 수화(hydration) 과정에 훅(hook)을 걸 수 있습니다.
실용적인 예를 들어보겠습니다. 미들웨어에서 HTTP 요청의 Accept-Language 헤더를 읽어 app.locale 설정값을 변경한다고 가정합시다. 이 값은 현재 프로세스에는 반영되지만, 큐 Job이 실행되는 별도의 프로세스에는 전달되지 않습니다. 컨텍스트 이벤트와 숨김 컨텍스트를 조합하면 이 값을 Job 프로세스에도 정확히 전달할 수 있습니다.
Dehydrating
Job이 큐에 디스패치될 때 현재 컨텍스트 데이터가 탈수(dehydrate) 되어 Job 페이로드에 함께 저장됩니다. Context::dehydrating 메서드로 이 과정에서 실행될 클로저를 등록할 수 있습니다. 이 클로저 안에서 큐 Job에 전달할 데이터를 수정할 수 있습니다.
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'));
});
}NOTE
dehydrating 콜백 내부에서는 Context 파사드를 사용하지 마세요. 파사드를 사용하면 현재 프로세스의 컨텍스트가 변경됩니다. 반드시 콜백에 전달된 $context 레포지토리 인스턴스만을 통해 변경하세요.
Hydrated
큐 Job이 실행될 때, Job과 함께 저장된 컨텍스트 데이터가 다시 현재 컨텍스트로 수화(hydrate) 됩니다. Context::hydrated 메서드로 이 과정에서 실행될 클로저를 등록할 수 있습니다.
hydrated 콜백도 AppServiceProvider의 boot 메서드에 등록하는 것이 일반적입니다.
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;
/**
* 애플리케이션 서비스를 초기화합니다.
*/
public function boot(): void
{
Context::hydrated(function (Repository $context) {
if ($context->hasHidden('locale')) {
Config::set('app.locale', $context->getHidden('locale'));
}
});
}NOTE
hydrated 콜백 내부에서도 Context 파사드를 사용하지 말고, 콜백에 전달된 $context 레포지토리 인스턴스만을 통해 변경하세요.