기존 Rails 로깅의 한계
-
Rails.logger.info("User created: id=#{user.id}, name=#{user.name}")와 같은 비정형 텍스트 로그는 파싱이 어렵고, 포맷 변경 시 분석 도구의 오류를 유발했습니다. -
로그 출처, 요청 ID, 정밀한 타임스탬프 등 일관된 메타데이터가 부족했습니다.
-
팀들은 JSON 포매터 래핑, 요청 ID 수동 추가 등 맞춤형 솔루션을 구축해야 했습니다.
Rails 8.1의 Rails.event 도입
-
Rails.event.notify("user.signup", user_id: 123, email: "[email protected]")와 같이 구조화된 이벤트를 발행합니다. -
모든 이벤트는
name,payload,tags,context,timestamp(나노초 정밀도),source_location등 표준화된 메타데이터를 포함합니다.
태그(Tags) 및 컨텍스트(Context) 추가
-
태그: 특정 코드 블록 내의 모든 이벤트에 도메인별 컨텍스트를 중첩하여 추가합니다. 예를 들어,
Rails.event.tagged("checkout")블록 내의 모든 이벤트는{ checkout: true }태그를 가집니다. 중첩 태그도 지원하여api,version,section등으로 세분화할 수 있습니다. -
컨텍스트: 요청 또는 작업 단위의 전체 실행 범위에 걸쳐 메타데이터를 설정합니다.
ApplicationController의before_action에서request_id,user_id,ip_address등을Rails.event.set_context로 설정하면 해당 요청 내의 모든 이벤트에 자동으로 포함됩니다.
디버그 모드
Rails.event.with_debug블록 내에서Rails.event.debug를 사용하면 디버그 모드가 활성화되었을 때만 이벤트가 보고됩니다. 이는 기존의 로그 레벨 대신notify(사용자 관심 이벤트)와debug(개발자 디버깅 관심 이벤트) 두 가지 명확한 방법을 제공합니다.
스키마화된 이벤트
OrderCompletedEvent와 같은 이벤트 객체를Rails.event.notify에 직접 전달하여 강력한 타입의 이벤트를 정의할 수 있습니다. 이는 코드베이스 전체의 일관성을 보장하며, 구독자는 객체 타입에 따라 직렬화를 처리할 수 있습니다.
이벤트 구독
-
이벤트 발행과 처리를 분리하기 위해 구독자 패턴을 사용합니다. 구독자는
emit(event)메서드를 구현해야 하며, 이 메서드는 이벤트 해시를 받습니다. -
LogSubscriber는Rails.logger를 통해 로그 라인을 생성하고,DatadogSubscriber는Datadog.log로 JSON 이벤트를 전송하는 등 여러 구독자를 등록하여 이벤트를 다양한 대상으로 라우팅할 수 있습니다.
테스트 지원
assert_event_reported및assert_no_event_reported와 같은 헬퍼를 제공하여 특정 이벤트가 예상 페이로드와 함께 발행되었는지 쉽게 검증할 수 있습니다.
주요 설계 결정
-
Fiber 기반 격리: 태그와 컨텍스트는 Fiber별로 범위가 지정되며, 자식 Fiber는 부모의 컨텍스트를 상속하지만 쓰기 시 복사(copy-on-write) 방식으로 작동합니다.
-
로그 레벨 없음:
info,warn,error와 같은 전통적인 로그 레벨 대신notify와debug두 가지 명확한 메서드를 제공합니다. -
Payload vs Tags vs Context: Payload는 특정 이벤트 정보, Tags는 중첩 가능한 도메인별 컨텍스트, Context는 요청/작업 수준의 메타데이터로 명확히 구분됩니다.