프로젝트를 운영하다 보면 사용자가 어떻게 행동하는지, 어떤 에러가 발생하는지 확인해야 할 필요가 있어요. 이를 파악하기 위해 로그 데이터를 분석해야 하는데, 로그를 남기는 것을 로깅(Logging) 이라고 해요.
로깅을 통해 기록된 데이터로 다음과 같은 정보를 확인할 수 있어요
- 사용자의 행동 패턴 추적
- 에러 발생 상황 파악
- 성능 병목 지점 발견
- A/B 테스트 결과 분석
이번 글에서는 Next.js 환경에서 자동화된 로깅 시스템을 구축하여 개발자가 비즈니스 로직에 집중할 수 있도록 하는 방법을 소개합니다.
기존 로깅 방식의 문제점
효율적인 로깅 구조가 없었을 땐 중요한 로직마다 수동으로 로깅 코드를 추가해야 했어요.
이런 방식의 문제점
1. 코드 가독성 저하
- 비즈니스 로직과 로깅 로직이 뒤섞여 있어요
- 실제 중요한 로직을 파악하기 어려워요
2. 일관성 부족
- 개발자마다 다른 로깅 형식을 사용해요
- 로그 분석 시 혼란을 야기해요
3. 누락 가능성
- 수동으로 로그를 추가하다 보면 빠뜨리기 쉬워요
- 중요한 에러 상황을 놓칠 수 있어요
4. 유지보수 어려움
- 로깅 형식 변경 시 모든 코드를 수정해야 해요
- 확장성이 떨어져요
5. 분산된 관리
- 클라이언트와 서버 로그가 따로 관리되어요
- 통합적인 분석이 어려워요
자동화된 로깅의 장점
이런 문제들을 해결하기 위해 자동화된 로깅 시스템을 구축하면:
- 깔끔한 코드: 비즈니스 로직에만 집중할 수 있어요
- 일관된 형식: 통일된 로그 구조로 분석이 쉬워요
- 누락 방지: 중요한 이벤트를 자동으로 캡처해요
- 통합 관리: 서버와 클라이언트 로그를 한 곳에서 관리해요
Next.js의 복잡한 실행 환경 이해하기
Next.js App Router는 여러 실행 환경에서 코드가 동작해요. 각 환경은 독립적으로 실행되기 때문에 환경별로 다른 로깅 전략이 필요해요.
주요 실행 환경
Client Component
- 사용자의 브라우저에서 실행
- JavaScript 런타임 에러, 네트워크 에러 발생 가능
window
객체 접근 가능
Server Component
- Next.js 서버에서 실행
- 데이터베이스 연결 에러, API 호출 에러 발생 가능
- Node.js 환경에서 동작
Middleware - Edge Runtime
- Edge Runtime에서 실행
- 모든 요청의 최상위에서 동작
- Server Action은 항상 middleware를 거친 다음 수행
- 인증, 리다이렉션 로직에서 에러 발생 가능
Error Pages
error.tsx
,not-found.tsx
- 예상치 못한 에러나 404 상황 처리
- Next.js 내부에서 핸들링
환경별 로깅 전략이 필요한 이유
각 환경은 서로 다른 특성을 가지고 있어요:
환경 | 특징 | 주요 에러 유형 |
---|---|---|
Client | 브라우저 API 사용 | 네트워크 에러, 런타임 에러 |
Server | Node.js API 사용 | 서버 에러, 데이터베이스 에러 |
Middleware | 제한된 API | 인증 에러, 리다이렉션 에러 |
Error Pages | 에러 복구 | 예상치 못한 에러 |
단계별 로깅 시스템 구축하기
이제 실제로 Next.js에서 자동화된 로깅 시스템을 구축합니다.
1단계: 기본 로거 함수 만들기
먼저 통일된 로그 형식을 위한 기본 로거 함수를 만들어요. 로그 수집 함수는 "use server"
를 명시해서 서버 액션으로 수행하도록 해요.
서버 액션은 어느 환경에서든 호출할 수 있고 여러 보안적 이점을 갖고 있어요. 로그에 민감 정보를 잘못 남겨서 보안 사고가 발생할 수도 있기 때문에 서버 액션을 사용했어요. 자세한 내용은 이전에 작성한 React Hook Form에서 Next.js Server Actions 사용하기에서 확인할 수 있어요.
2단계: 클라이언트 에러 자동 수집
브라우저에서 발생하는 에러를 자동으로 수집하는 컴포넌트를 만들어요.
- 브라우저에서 발생하는 런타임 에러는 모두
window.onerror
및window.onunhandledrejection
로 전파돼요. - 이 원리를 이용해서 하나의 컴포넌트에서 모든 런타임 에러를 캡처할 수 있어요.
만든 RuntimeLogger를 앱 전체에 적용해요.
3단계: Server Components 에러 자동 수집
아래 이슈에서 힌트를 얻었어요
Next.JS는 서버 사이드에서 에러가 발생하면 error.tsx로 fallback되어요. 이때 별도의 로깅을 남기고자 하여도, 빌드 환경에선 로깅을 수행할 수 없어요. error.tsx에서 error.message
를 보면, 아래 에러 메시지를 보게 돼요.
[!danger]
An error occurend in the Server Component render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additionnal details about the nature of the error. Error digest : XXXXXXXXXXXXX
이 메시지를 보면, 빌드 환경에선 에러 메시지가 숨겨져 있어요. 이를 해결하기 위해 공식문서에서 제공하는 instrumentation.ts
를 사용해요.
[!info]
instrumentation.ts는 최근까지
experimental
상태였지만, 현재 v15.3 기준 정식 기능이 되어있어요. 아직까지 AI에 질문하거나 구글링하여도 Server Components의 에러를 공통화할 수 없고, 에러가 발생할 수 있는 지점마다 로깅 함수를 호출하거나 Sentry를 보내라고 답변해요.

하지만 아래 방법으로 로깅을 공통화할 수 있어요.
각 Parameter별로 아래 정보를 담고있어요.
err
:Error
객체request
:- path: 현재 페이지 경로
- method: GET
- headers: next/headers 정보
- onRequestError로 콜백된 실행 컨텍스트 내에선 next/headers 를 호출해도 빈 값이 나와요. 대신 request.headers를 사용할 수 있어요.
context
:- routerKind: 라우터 종류 (
"App Router"
|"Page Router"
) - routePath: 현재 페이지 경로인데, dynamic route일 경우
"/reserve/[id]"
처럼 표현되어요. - routeType:
"render"
(아직 무엇이 더 존재하는지 확인 못했어요..) - renderSource:
"react-server-components"
- routerKind: 라우터 종류 (
4단계: API 호출 로깅
API 호출 시 로깅은 각 fetch 함수 호출부에서 수행해요.
- 에러가 발생할 경우, RSC, CSC는
throw
를 통해 공통 에러 처리 영역으로 전파할 수 있지만, Server Action 에서 발생한 에러는throw
를 권장하지 않아요. 공식문서 - 기본 형식 외 추가적인 메타 데이터를 담기 위해, 공통 fetch 함수를 만들어요.
사용 예시
이제 깔끔한 코드로 API를 호출할 수 있어요:
5단계: Error pages 처리
참조: Next.js Learn Chapter 13 - Handling Errors
error.tsx와 not-found는 각 고유 역할별로 에러를 처리해요.
error.tsx
이미 클라이언트 사이드 에러는 로깅을 처리했기 때문에, 이곳에선 별도의 로깅을 처리하지 않아요.
서버 사이드 에러는 production 환경에서 전파된 경우, 데이터가 숨겨지기 때문에 로깅할 수 없어요.
- 클라이언트 사이드 에러:
window.onerror
,window.onunhandledrejection
로 처리 - 서버 사이드 에러: instrumentation.ts에서
onRequestError
로 처리
[!info]
배포된 환경에선 서버 사이드에서 throw된 에러는 error.tsx에서
error.message
를 참조하면 "An error occurred in the Server Components render." 라는 메시지를 볼 수 있어요.
Next.JS는 내부적으로 서버에서 민감 정보가 클라이언트로 유출되는 것을 방지하기 위해 메시지를 일부로 제한해요. 그렇기 때문에 error.tsx에 도달하기 전에 로깅을 수행해야 해요.
not-found.tsx
not-found.tsx는 404 에러를 처리해요.
만약, Programmatic Navigation을 통해 404 에러를 발생시킨거라면, 이 페이지에서 로그를 수집할 수 있어요.
6단계: middleware.ts 에러 처리
middleware는:
- 기본적으로 Edge Runtime에서 실행돼요.
- 이는
layout.js
보다 더 상위 레벨로, 사용할 수 있는 API도 제한되어 있어요. 참조
middleware에서는 공통 처리가 불가능해서, 위 5단계: Error pages 처리처럼 별도의 logger
함수를 추가해야해요.
로그 분석을 위한 쿼리 예시
구축한 로깅 시스템의 데이터를 효과적으로 분석하기 위한 쿼리들을 소개해요. 이 예시들은 AWS CloudWatch Logs Insights를 기준으로 작성되었지만, 다른 로깅 서비스에서도 비슷하게 활용할 수 있어요.
기본 분석 쿼리
성능 분석 쿼리
결과
위 방식으로 Next.JS에서 효율적인 로깅 시스템을 구축할 수 있었어요. 비즈니스 로직 사이사이 끼워져있던 로깅 함수를 모두 거둬냈고, 로깅을 실수로 추가하기 못해 발생하는 문제도 없어졌어요. 이제 개발자들은 더 이상 로깅 코드를 추가하는 것을 걱정하지 않아도 되고, 비즈니스 로직에만 집중할 수 있게 되었어요.
최종적으로 중요한건 어디서 공통으로 처리가 가능할지 고려하는거였어요. Next.JS는 다양한 실행 환경을 가지고 있어서 환경별로 어떻게 처리할 수 있을지를 가장 많이 고민했어요. 이 부분을 잘 고려하면 코드 가독성과 유지보수성을 높일 수 있어요.