메인 콘텐츠로 건너뛰기
패키지 고유의 로그 출력을 제공하고 싶을 때는 로거를 만드는 구조와 사용하는 애플리케이션이 선택하는 채널 설정을 분리합니다. 드라이버를 등록하는 것만으로 기존 채널이 다시 만들어지는 것은 아니며, 설정을 변경한 후에도 이전 Handler가 사용되는 경우가 있습니다. 이 페이지는 Laravel Framework v13.39.0의 구현을 확인한 내용입니다. 기본 조작은 로깅을, 프로바이더 준비는 패키지 개발을 참조하세요.

필요한 확장 포인트 선택하기

기존 드라이버로 구현할 수 있다면 독자적인 드라이버를 늘릴 필요는 없습니다. via 클래스와 tap 클래스는 컨테이너에서 해석되므로 생성자 주입도 사용할 수 있습니다. tap은 채널 생성 시의 조정이며, 로그를 1건 기록할 때마다 실행되는 훅이 아닙니다.
logging.channels.audit의 audit은 채널 이름입니다. 그 안의 driver는 생성 방법의 이름입니다. Log::channel('audit')로 선택하는 것은 채널 이름이므로, 나중에 드라이버를 변경해도 호출하는 쪽의 이름을 유지할 수 있습니다.

먼저 custom과 via로 팩토리를 공개하기

가상의 Acme\Audit 패키지에서 파일 출력용 팩토리를 제공합니다. PSR-4로 로드할 수 있는 src/Logging/CreateAuditLogger.php에 배치합니다.
사용하는 애플리케이션에서는 config/logging.php의 기존 channels 배열에 다음 요소를 추가합니다. 기존 default나 stack 설정은 그대로 둡니다.
LogManager::createCustomDriver()는 via 클래스를 컨테이너에서 가져와 채널의 설정 배열을 유일한 인수로 호출합니다. 이 방식만 사용한다면 드라이버 등록용 프로바이더는 필요 없습니다.
이 팩토리에서는 level을 StreamHandler에 명시적으로 전달합니다. custom 팩토리가 반환하는 로거에 대해 LogManager가 설정의 level을 일률적으로 적용하는 것은 아닙니다. 독자적인 설정 항목을 추가한 경우에도 팩토리 쪽에서 반영합니다.
단순히 파일의 출력 위치만 바꾸는 것이라면 single 드라이버로 충분합니다. 이 예는 패키지 고유의 Handler나 초기화를 포함하기 위한 최소 구성입니다. via는 설정 캐시에 저장할 수 있는 클래스 이름으로 하고, 설정 배열에 클로저나 로거 인스턴스를 저장하지 마세요.

독자적인 드라이버 이름을 제공할 때는 extend를 사용하기

사용하는 쪽에 driver: acme-audit라는 설정을 공개하고 싶다면, 같은 팩토리를 사용하는 드라이버를 등록합니다. src/AuditServiceProvider.php의 boot()에서 Laravel의 log 서비스를 확장합니다.
프로바이더는 패키지 자동 검색 등으로 로드합니다. 사용하는 쪽의 channels.audit은 앞의 설정에서 다음 설정으로 교체합니다. via는 더 이상 필요하지 않습니다.
  • Laravel의 LogServiceProvider는 log를 singleton으로 등록합니다. callAfterResolving()은 아직 해석되지 않았다면 기다리고, 이미 해석되었다면 그 인스턴스에도 등록을 반영합니다. 자세한 내용은 서비스 해석 후 훅을 참조하세요.
  • extend()는 생성용 클로저를 등록할 뿐입니다. 채널 생성이나 로그 기록은 이 콜백을 등록하는 시점에는 수행하지 않습니다.
  • callCustomCreator()는 클로저에 $app과 채널 설정 배열을 전달합니다. via 팩토리에 전달되는 인수와는 다릅니다.
  • 같은 드라이버 이름을 다시 등록하면 생성용 클로저를 덮어씁니다. single 등의 내장 이름이나 다른 패키지와 충돌하는 이름은 사용하지 마세요.
확인한 구현에서는 extend()에 전달한 익명 클로저가 LogManager의 스코프로 다시 바인딩됩니다. 비static 클로저의 $this는 서비스 프로바이더가 아닙니다. 예와 같이 static 클로저에서 인수 $app을 사용하고, 프로바이더의 $this에 의존하지 않는 구현으로 합니다.
패키지는 드라이버를 제공하고, 어느 채널이나 stack에서 사용할지는 사용하는 쪽이 선택합니다. 프로바이더에서 logging.default나 기존 logging.channels 전체를 덮어쓰지 않는 설계로 하세요.

채널은 이름별로 보관된다

LogManager::get()은 해석한 채널을 $channels에 저장합니다. 같은 이름의 채널을 가져오면 설정 배열을 다시 읽지 않고 보관된 객체를 반환합니다. 이는 파일에 저장하는 설정 캐시와는 별개인, 현재 LogManager 안의 객체 보관입니다. config() 변경이나 extend() 재등록만으로는 이미 생성된 Handler가 교체되지 않습니다.
이 예의 $same은 $before와 같은 객체입니다. forgetChannel('audit')가 Manager의 보관을 해제하면, 다음 가져오기에서 새로운 설정으로 $after가 생성됩니다.

forgetChannel은 기존 참조를 갱신하지 않는다

forgetChannel()이 수행하는 것은 Manager 안의 엔트리 삭제입니다.
  • $before를 보관하고 있는 서비스는 그 이후에도 이전 로거를 사용할 수 있습니다. 기존 참조를 새로운 객체로 교체하는 조작이 아닙니다.
  • Log::channel('stack') 등으로 이미 생성된 stack은 자식 채널의 Handler와 Processor를 모은 독립된 Monolog를 가집니다. 자식만 forgetChannel()해도 기존 stack의 구성은 바뀌지 않습니다. 필요하다면 그 stack의 채널 이름에 대해서도 보관을 해제하고 다시 가져옵니다.
  • forgetChannel() 자체는 Handler의 close()를 호출하지 않습니다. 연결이나 버퍼의 종료 처리까지 보장하는 API가 아닙니다.
일반적인 배포에서는 설정 캐시 갱신과 사용 중인 장기 실행 워커의 재시작을 운영 절차에 포함합니다. 요청마다 설정을 덮어쓰고 다시 해석하는 구조에 의존하지 말고, 서비스의 라이프사이클도 고려하세요.

stack에 포함하려면 Monolog 호환 조작도 필요

독자적인 드라이버는 Psr\Log\LoggerInterface 구현을 반환할 수 있지만, Laravel의 createStackDriver()는 자식 채널에 대해 getHandlers()와 getProcessors()를 호출합니다. 이들은 PSR 인터페이스에 포함되지 않습니다. 임의의 PSR 로거를 반환한다고 해서 stack에서도 동작한다고는 할 수 없으므로, stack에 포함하는 것도 제공하는 패키지에서는 예와 같이 Monolog를 반환하거나 그 사용 범위를 명시하고 테스트합니다.

목뿐만 아니라 실제 채널 해석을 검증하기

Log::shouldReceive('channel')만으로는 프로바이더 등록, 팩토리 선택, 설정 반영을 확인할 수 없습니다. 다음은 패키지 테스트 환경에서 실제 Manager와 팩토리를 거치는 예입니다. 테스트용 출력 위치에는 php://memory를 지정합니다. 여기서는 채널 구축과 Handler 설정을 검증하며, 로그 기록 자체는 수행하지 않습니다.
LogManager::get()은 채널 생성 중에 발생한 예외를 포착하고 emergency logger를 반환하는 경우가 있습니다. “예외가 발생하지 않았다”, “LoggerInterface였다”는 확인만으로는 목적한 채널이 구축되었다는 증거가 되지 않습니다. 위의 테스트에서는 Monolog의 이름, Handler, 로그 레벨도 확인해 폴백을 성공으로 오인하지 않도록 하고 있습니다. 생성 후의 기록 오류까지 같은 구조로 반드시 포착되는 것은 아닙니다.

장기 유지보수에서 추가하는 검증

참조한 1차 정보

마지막 수정일 2026년 10월 28일