> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Context(컨텍스트)

> Laravel의 Context 기능을 사용해 요청·잡·명령어를 넘나들며 정보를 공유하고 로그에 자동 부여하는 방법을 설명합니다.

## Context 란

Laravel의 Context 기능은 요청·큐 잡·명령어의 실행을 넘나들며 정보를 기록·공유하기 위한 구조입니다.
`Illuminate\Support\Facades\Context` 파사드를 통해 정보를 추가하면 그 정보는 애플리케이션이 기록하는 모든 로그 엔트리에 자동으로 부여됩니다.

이에 따라 개별 로그 호출에 전달한 정보와 Context가 유지하는 공유 정보를 명확하게 구별할 수 있습니다.
분산 시스템이나 큐를 사용한 아키텍처에서 트레이싱을 할 때 특히 유용합니다.

### 컨텍스트의 전파 플로우

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Middleware
    participant Controller
    participant Queue
    participant Job

    Client->>Middleware: HTTP 요청
    Middleware->>Middleware: Context::add('trace_id', uuid)
    Middleware->>Controller: next($request)
    Controller->>Controller: Log::info(...) — trace_id 자동 부여
    Controller->>Queue: ProcessPodcast::dispatch()
    Queue->>Job: 컨텍스트를 직렬화하여 전송
    Job->>Job: 컨텍스트를 복원(Hydrate)
    Job->>Job: Log::info(...) — trace_id 자동 부여
```

## 기본적인 사용법

가장 전형적인 사용법은 미들웨어에서 `trace_id`를 설정하는 것입니다. 이후의 모든 로그 엔트리에 자동으로 포함됩니다.

<Steps>
  <Step title="미들웨어 작성">
    ```shell theme={null}
    php artisan make:middleware AddContext
    ```
  </Step>

  <Step title="Context 에 트레이스 ID 를 추가">
    ```php theme={null}
    <?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);
        }
    }
    ```
  </Step>

  <Step title="미들웨어 등록">
    `bootstrap/app.php`에서 글로벌 미들웨어로 등록합니다.

    ```php theme={null}
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(\App\Http\Middleware\AddContext::class);
    })
    ```
  </Step>
</Steps>

이 설정 후, 컨트롤러나 서비스에서 기록하는 로그에는 `url`과 `trace_id`가 자동으로 부여됩니다.

```php theme={null}
Log::info('User authenticated.', ['auth_id' => Auth::id()]);
```

```text theme={null}
User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
```

## 컨텍스트에의 쓰기

### add — 값 추가

```php theme={null}
use Illuminate\Support\Facades\Context;

Context::add('key', 'value');

// 여러 개를 함께 추가
Context::add([
    'first_key'  => 'value',
    'second_key' => 'value',
]);
```

`add`는 기존 키를 덮어씁니다. 키가 존재하지 않는 경우에만 추가하고 싶을 때는 `addIf`를 사용합니다.

```php theme={null}
Context::add('key', 'first');
Context::addIf('key', 'second');

Context::get('key');
// "first" — 덮어써지지 않음
```

### increment / decrement — 카운터 관리

수치를 증감시키는 전용 메서드입니다. 두 번째 인수로 변화량을 지정할 수 있습니다.

```php theme={null}
Context::increment('records_added');
Context::increment('records_added', 5);

Context::decrement('records_added');
Context::decrement('records_added', 5);
```

### when — 조건부로 추가

`when` 메서드를 사용하면 조건이 `true`일 때·`false`일 때 각각 다른 데이터를 추가할 수 있습니다.

```php theme={null}
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 — 스택에 추가

Context는 리스트 형식의 데이터를 유지하는 "스택"을 지원합니다.
`push`를 사용하면 추가한 순서로 데이터가 쌓여 갑니다.

```php theme={null}
Context::push('breadcrumbs', 'first_value');
Context::push('breadcrumbs', 'second_value', 'third_value');

Context::get('breadcrumbs');
// ['first_value', 'second_value', 'third_value']
```

쿼리의 실행 이력을 스택으로 기록하는 예시입니다.

```php theme={null}
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;

// AppServiceProvider.php 의 boot 메서드에서 등록
DB::listen(function ($event) {
    Context::push('queries', [$event->time, $event->sql]);
});
```

## 컨텍스트 취득

### get / all

```php theme={null}
$value = Context::get('key');

// 모두 취득
$data = Context::all();
```

### only / except — 일부만 취득

```php theme={null}
$data = Context::only(['first_key', 'second_key']);

$data = Context::except(['first_key']);
```

### pull / pop — 취득해서 삭제

`pull`은 키의 값을 취득함과 동시에 컨텍스트에서 삭제합니다.

```php theme={null}
$value = Context::pull('key');
```

스택에서 마지막 값을 꺼내려면 `pop`을 사용합니다.

```php theme={null}
Context::push('breadcrumbs', 'first_value', 'second_value');

Context::pop('breadcrumbs');
// 'second_value'

Context::get('breadcrumbs');
// ['first_value']
```

### remember — 존재하지 않으면 설정해서 반환

```php theme={null}
$permissions = Context::remember(
    'user-permissions',
    fn () => $user->permissions,
);
```

### has / missing — 키의 존재 확인

```php theme={null}
if (Context::has('key')) {
    // ...
}

if (Context::missing('key')) {
    // ...
}
```

<Info>
  `has`는 `null`이 저장되어 있어도 `true`를 반환합니다. 키가 등록되어 있는지 여부만을 확인합니다.
</Info>

## 컨텍스트 삭제

`forget`으로 키를 삭제합니다.

```php theme={null}
Context::add(['first_key' => 1, 'second_key' => 2]);

Context::forget('first_key');

Context::all();
// ['second_key' => 2]

// 여러 개를 함께 삭제
Context::forget(['first_key', 'second_key']);
```

## 스코프 부착 컨텍스트

`scope` 메서드를 사용하면 클로저의 실행 중에만 컨텍스트를 일시적으로 변경하고, 실행 후에 원래 상태로 자동으로 되돌릴 수 있습니다.
테스트나 국소적인 처리에서 일시적인 추가 정보를 로그에 포함시키고 싶을 때 편리합니다.

```php theme={null}
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]
```

<Warning>
  스코프 내에서 객체를 변경한 경우, 그 변경은 스코프의 밖에도 반영됩니다. 원시값을 사용하는 경우는 문제 없습니다.
</Warning>

## Hidden Context

로그에 출력하고 싶지 않은 데이터(비밀번호·API 키·개인 식별 정보 등)는 Hidden Context에 저장합니다.
일반적인 `get` 메서드로는 취득할 수 없으며, `getHidden` 등의 전용 메서드로만 접근할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Context;

Context::addHidden('key', 'value');

Context::getHidden('key');
// 'value'

Context::get('key');
// null — 일반 get 으로는 취득 불가능
```

Hidden Context는 일반 컨텍스트와 같은 메서드군을 갖습니다.

```php theme={null}
Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::exceptHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::missingHidden(/* ... */);
Context::forgetHidden(/* ... */);
```

## 큐 잡에의 인계

잡을 큐에 디스패치하면 현재의 컨텍스트가 자동으로 직렬화되어 잡의 페이로드에 포함됩니다.
잡 실행 시에 원래의 컨텍스트가 복원되기 때문에, 요청에서 부여한 `trace_id`가 큐 상의 로그에도 자동으로 인계됩니다.

```php theme={null}
// 미들웨어에서 설정
Context::add('trace_id', Str::uuid()->toString());

// 컨트롤러에서 잡을 디스패치
ProcessPodcast::dispatch($podcast);
```

```php theme={null}
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    public function handle(): void
    {
        Log::info('Processing podcast.', ['podcast_id' => $this->podcast->id]);
    }
}
```

```text theme={null}
Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
```

요청 시의 `trace_id`가 큐 상의 로그에도 포함되는 것을 확인할 수 있습니다.

### Dehydrating — 잡 전송 시의 커스터마이즈

`Context::dehydrating`을 사용하면 잡 전송의 직전에 컨텍스트를 가공할 수 있습니다.
예를 들어 `Accept-Language` 헤더로 정해지는 로케일을 큐에 전달하고 싶은 경우에 사용합니다.

```php theme={null}
// AppServiceProvider.php

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'));
    });
}
```

<Warning>
  `dehydrating` 콜백 내에서는 `Context` 파사드를 사용하지 말고, 콜백에 전달된 `$context` 리포지토리만 조작해 주십시오.
  파사드를 사용하면 현재 프로세스의 컨텍스트를 변경하게 됩니다.
</Warning>

### Hydrated — 잡 실행 시의 복원

`Context::hydrated`를 사용하면 잡 실행의 직전에 컨텍스트가 복원된 타이밍에 처리를 추가할 수 있습니다.
예를 들어 저장해 두었던 로케일을 설정 파일에 반영합니다.

```php theme={null}
// AppServiceProvider.php

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'));
        }
    });
}
```

<Warning>
  `hydrated` 콜백 내에서도 `Context` 파사드는 사용하지 말고, 전달된 `$context` 리포지토리만 조작해 주십시오.
</Warning>

## 정리

<AccordionGroup>
  <Accordion title="Context vs Log::withContext 의 차이">
    |            | `Context`             | `Log::withContext` |
    | ---------- | --------------------- | ------------------ |
    | 대상         | 모든 로그 채널              | 특정 채널만             |
    | 큐 잡에의 인계   | 자동(Dehydrate/Hydrate) | 없음                 |
    | Hidden 데이터 | 지원                    | 없음                 |
    | 용도         | 트레이싱·분산 시스템           | 채널 고유의 메타데이터       |
  </Accordion>

  <Accordion title="Hidden Context 를 사용해야 할 데이터">
    Hidden Context는 로그에 출력되지 않기 때문에, 다음과 같은 데이터를 안전하게 저장할 수 있습니다.

    * 세션 ID나 사용자 ID(로그에 남기고 싶지 않은 경우)
    * API 키나 인증 토큰
    * 로케일이나 설정값(큐에 인계하고 싶지만 로그에는 불필요)
    * 내부적인 플래그나 상태
  </Accordion>

  <Accordion title="Dehydrate/Hydrate 의 자주 있는 사용 패턴">
    1. **로케일의 인계**: `dehydrating`으로 `app.locale`을 Hidden Context에 저장하고, `hydrated`에서 `Config::set`으로 복원.
    2. **인증 정보의 전파**: 요청에서 인증한 사용자의 정보를 큐 잡에서도 참조할 수 있도록 함.
    3. **테넌트 ID**: 멀티테넌트 앱에서 테넌트 식별자를 큐를 넘나들며 공유.
  </Accordion>
</AccordionGroup>


## Related topics

- [세션 컨텍스트와 필터링](/ko/packages/laravel-copilot-sdk/session-context.md)
- [로깅](/ko/logging.md)
- [Laravel AI SDK](/ko/ai-sdk.md)
- [시작하기 - GitHub Copilot SDK for Laravel](/ko/packages/laravel-copilot-sdk/getting-started.md)
- [스트리밍 이벤트](/ko/packages/laravel-copilot-sdk/streaming-events.md)
