init: 클린 기반 auth 서버 설계
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
# Exception Log 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 예외를 로그로 남길 때의 기준을 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- 같은 실패를 여러 레이어에서 중복 로그하는 일을 줄인다
|
||||
- 대표 예외 로그가 운영에 필요한 맥락을 충분히 담게 한다
|
||||
- 예외 메시지, stack trace, 민감정보 노출을 통제한다
|
||||
- 예외 처리와 예외 로깅의 책임을 분리한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: Spring Framework / Spring Boot / OWASP 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 기능 위에 일반적인 운영 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 예외 처리와 예외 로깅은 같은 문제가 아니다
|
||||
|
||||
Spring은 @ExceptionHandler, @ControllerAdvice, @RestControllerAdvice, ResponseEntityExceptionHandler로 예외를 HTTP 응답으로 변환할 수 있게 합니다. 하지만 예외를 응답으로 바꾸는 위치가 곧 예외를 항상 거기서만 로그해야 한다는 뜻은 아닙니다. 이 프로젝트에서는 예외 변환 책임과 대표 예외 로그 책임을 구분합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예외를 HTTP/API 응답으로 만드는 책임과
|
||||
- 예외를 운영 로그로 남기는 책임을
|
||||
- 같은 지점에 둘 수도 있지만, 개념적으로는 구분한다
|
||||
|
||||
### 3.2 대표 예외 로그는 한 번만 남긴다
|
||||
|
||||
Spring은 예외를 전역 advice에서 일관되게 처리할 수 있게 해 주므로, 같은 예외가 controller, service, client, advice에서 모두 stack trace와 함께 반복 기록될 필요는 없습니다. 실무적으로도 한 실패에 대해 대표 ERROR 로그 한 번을 남기고, 나머지는 보조 맥락만 남기는 편이 검색·알림·분석 품질이 좋습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 한 실패에 대해 대표 예외 로그 한 번을 원칙으로 한다
|
||||
- 하위 계층은 필요하면 DEBUG 또는 WARN으로 맥락만 남긴다
|
||||
- 같은 stack trace를 여러 레이어에서 반복 ERROR로 남기지 않는다
|
||||
|
||||
### 3.3 예외 로그는 “무슨 일이 왜 어디서 실패했는지”를 설명해야 한다
|
||||
|
||||
Spring Boot는 기본 로그에 level, thread, logger, correlation 정보를 담을 수 있고, structured logging도 지원합니다. 따라서 예외 로그도 단순 ex.getMessage()가 아니라, 요청/작업/외부 시스템/소요시간/에러 코드 같은 운영 키를 함께 남겨야 의미가 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예외 로그는 사건 설명을 먼저 쓴다
|
||||
- 그 뒤에 운영 필드(key=value)를 붙인다
|
||||
- 예외 객체(stack trace)는 마지막 인자로 넘긴다
|
||||
|
||||
권장 예:
|
||||
|
||||
```text
|
||||
Failed external auth request. provider=keycloak actorId=u_001 requestPath=/api/v1/sessions errorCode=UPSTREAM_AUTH_SERVER_UNAVAILABLE
|
||||
```
|
||||
|
||||
## 4. 대표 로그 위치 규칙
|
||||
|
||||
### 4.1 HTTP 요청 실패의 대표 로그는 공통 경계에서 남긴다
|
||||
|
||||
Spring MVC는 전역 @ControllerAdvice/@RestControllerAdvice에서 controller 예외를 공통 처리할 수 있습니다. 따라서 일반 HTTP 요청 실패의 대표 예외 로그는 보통 전역 예외 처리 경계 또는 요청 완료 공통 로깅 경계 중 한 곳에서 일관되게 남기는 것이 적절합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 일반 요청 실패는 공통 advice 또는 공통 요청 로깅 경계에서 대표 로그를 남긴다
|
||||
- controller 메서드마다 try-catch + log.error를 반복하지 않는다
|
||||
- controller local @ExceptionHandler가 있어도 대표 예외 로그 위치는 프로젝트 단위로 일관되게 유지한다
|
||||
|
||||
### 4.2 외부 API 실패의 대표 로그는 “최종 실패가 확정된 경계”에서 남긴다
|
||||
|
||||
외부 연동은 client/adapter 계층에서 많은 중간 실패가 생길 수 있습니다. 이런 중간 실패를 모두 ERROR로 남기면 재시도 후 성공한 케이스도 장애처럼 보일 수 있습니다. 따라서 대표 로그는 최종적으로 호출 결과가 실패로 확정된 시점에 남기는 것이 좋습니다. 이 원칙은 앞서 정한 log level 기준과도 맞습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 재시도 전 단일 실패는 기본적으로 DEBUG 또는 WARN
|
||||
- 재시도 후 최종 실패가 되면 대표 ERROR
|
||||
- fallback으로 정상 복구되면 WARN 또는 INFO로 남기고 ERROR로 과장하지 않는다
|
||||
|
||||
### 4.3 배치/스케줄/비동기 작업은 작업 경계에서 대표 로그를 남긴다
|
||||
|
||||
Spring의 예외 처리 문맥은 HTTP controller만을 위한 것이 아니므로, 스케줄/비동기/배치 작업에서는 작업 진입점이나 orchestration 경계에서 대표 예외 로그를 남겨야 합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 스케줄 작업은 job/unit-of-work 경계에서 대표 로그를 남긴다
|
||||
- 비동기 후속 작업도 작업 단위 식별자와 함께 실패를 기록한다
|
||||
- 내부 helper 메서드들이 모두 각자 ERROR를 찍지 않는다
|
||||
|
||||
## 5. 로그 레벨 규칙
|
||||
|
||||
### 5.1 최종 실패 예외는 ERROR
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청/작업이 최종 실패로 끝났으면 ERROR
|
||||
- 응답이 5xx이거나, 작업 결과가 실패로 종료되면 ERROR
|
||||
- 복구되지 않은 예외는 ERROR
|
||||
|
||||
### 5.2 복구된 예외는 WARN 또는 DEBUG
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 재시도 후 성공
|
||||
- fallback 후 성공
|
||||
- 대체 경로로 정상 처리
|
||||
|
||||
이 경우 대표 로그는 WARN 또는 필요 시 INFO
|
||||
|
||||
stack trace가 꼭 필요하지 않으면 DEBUG/WARN 요약 로그만 남긴다
|
||||
|
||||
### 5.3 예상 가능한 클라이언트 오류는 무조건 ERROR로 남기지 않는다
|
||||
|
||||
Spring에서 validation 예외나 request parsing 예외도 전역 advice에서 처리할 수 있지만, 그것이 모두 서버 이상을 뜻하는 것은 아닙니다. OWASP도 보안상 가치 있는 실패는 남기라고 하지만, 민감정보 노출 없이 맥락 중심으로 남기라고 권고합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- validation 실패
|
||||
- 잘못된 요청 파라미터
|
||||
- business rule rejection
|
||||
- 권한 없음
|
||||
|
||||
같은 예상 가능한 4xx는 기본적으로 INFO 또는 WARN
|
||||
|
||||
대량 이상 징후가 아니면 ERROR로 과장하지 않는다
|
||||
|
||||
## 6. 메시지 구성 규칙
|
||||
|
||||
### 6.1 예외 로그 제목은 예외 메시지가 아니라 사건 설명이다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- log.error(ex.getMessage(), ex)를 기본 금지
|
||||
- 로그 제목은 애플리케이션이 통제하는 사건 설명으로 쓴다
|
||||
- 예외 메시지는 보조 정보일 뿐, 로그 제목의 전부가 아니다
|
||||
|
||||
권장:
|
||||
|
||||
```java
|
||||
log.error("Failed to create session. actorId={} requestPath={} errorCode={}",
|
||||
actorId, requestPath, errorCode, ex);
|
||||
```
|
||||
|
||||
### 6.2 대표 예외 로그의 권장 필드
|
||||
|
||||
권장 필드:
|
||||
|
||||
- traceId 또는 correlation ID 연결 가능 정보
|
||||
- requestPath
|
||||
- method
|
||||
- actorId
|
||||
- operation
|
||||
- resourceId
|
||||
- externalSystem
|
||||
- errorCode
|
||||
- status
|
||||
- durationMs
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 모든 필드를 다 강제하지는 않는다
|
||||
- 해당 실패를 운영에서 추적하는 데 필요한 최소 필드를 남긴다
|
||||
- 같은 종류의 예외 로그는 같은 필드 이름을 유지한다
|
||||
|
||||
### 6.3 stack trace만으로 맥락을 대체하지 않는다
|
||||
|
||||
Spring Boot는 structured logging에서 stack trace 출력 방식도 조정할 수 있지만, stack trace는 어디까지나 원인 분석용입니다. 운영자가 “무슨 요청/작업이 왜 실패했는지”를 빠르게 이해하려면 메시지 맥락이 필요합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- stack trace가 있으니 메시지를 대충 쓰지 않는다
|
||||
- 메시지는 사건 설명과 운영 키를 담고
|
||||
- stack trace는 원인 분석을 보조한다
|
||||
|
||||
## 7. stack trace 규칙
|
||||
|
||||
### 7.1 대표 ERROR 로그에는 기본적으로 stack trace를 포함한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 최종 실패를 나타내는 대표 ERROR 로그는 기본적으로 예외 객체를 함께 남긴다
|
||||
- stack trace 없는 ERROR 로그는 원인 분석에 불리하므로 예외적 경우에만 허용한다
|
||||
|
||||
### 7.2 WARN/INFO에서는 stack trace를 신중하게 남긴다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 재시도 후 성공, fallback 후 성공 같은 경우에는 stack trace 없이 요약 로그를 우선한다
|
||||
- 같은 원인의 경고가 고빈도로 반복될 수 있으면 stack trace를 매번 남기지 않는다
|
||||
- 필요하면 최초 1회만 stack trace, 이후는 요약만 남기는 전략을 검토한다
|
||||
|
||||
### 7.3 너무 큰 stack trace는 구조화 포맷과 수집 비용을 고려한다
|
||||
|
||||
Spring Boot는 structured logging에서 stack trace 포함과 길이, 출력 방식을 조정할 수 있습니다. 큰 예외가 자주 발생하는 시스템에서는 수집 비용과 검색성을 고려해야 합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 대량 반복 예외의 stack trace 출력 정책은 운영 비용을 고려해 조정한다
|
||||
- 하지만 비용을 이유로 대표 실패의 원인 정보가 완전히 사라지게 만들지는 않는다
|
||||
|
||||
## 8. 민감정보 규칙
|
||||
|
||||
### 8.1 예외 로그도 PII/sensitive 규칙을 그대로 따른다
|
||||
|
||||
OWASP는 세션 식별값, 토큰, 비밀번호, 민감 PII, 키/비밀값 등은 직접 로그에 남기지 말라고 권고합니다. 예외 로그도 예외가 아닙니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예외 로그 제목에 민감정보 원문 금지
|
||||
- 예외 메시지에 민감정보가 포함될 수 있으면 그대로 재사용 금지
|
||||
- request/response body 전문을 예외 맥락으로 붙이지 않는다
|
||||
- 외부 시스템 에러 본문도 원문 그대로 남기지 않는다
|
||||
|
||||
### 8.2 stack trace에도 비밀값이 섞일 수 있음을 전제한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예외 생성 메시지에 비밀값을 넣지 않는 것이 우선
|
||||
- 예외에 포함된 URL, 헤더, payload, connection string, token 등을 주의한다
|
||||
- 비밀값이 exception message에 들어가도록 코드를 짜지 않는다
|
||||
|
||||
## 9. 번역(translation) 규칙
|
||||
|
||||
### 9.1 내부 예외와 외부 응답 메시지를 분리한다
|
||||
|
||||
Spring은 @ExceptionHandler와 ResponseEntityExceptionHandler로 응답 변환을 지원합니다. 이 프로젝트는 예외 로그와 API 응답 메시지도 분리합니다. 즉, 로그에는 운영에 필요한 안전한 맥락을 남기고, 응답은 ErrorCode와 외부 메시지 규약으로 보냅니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 로그 메시지 ≠ API 응답 메시지
|
||||
- ex.getMessage()를 API 응답에도, 로그 제목에도 그대로 재사용하지 않는다
|
||||
- advice는 응답 변환을, 대표 로그는 운영 맥락 기록을 담당한다
|
||||
|
||||
### 9.2 예외 번역 계층이 있다면 원인 체인을 잃지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- external/client 예외 → integration 예외 → application 예외로 번역할 수 있다
|
||||
- 이 과정에서 root cause를 완전히 잃지 않는다
|
||||
- 대표 로그는 번역된 비즈니스 의미와 원인 예외를 함께 남길 수 있어야 한다
|
||||
|
||||
## 10. 위치별 세부 규칙
|
||||
|
||||
### 10.1 controller
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- controller에서 try-catch + log.error를 기본 금지
|
||||
- 공통 advice가 있는 구조에서는 controller는 예외를 그대로 위로 전파한다
|
||||
- endpoint-local 특별 정책이 있어도 대표 예외 로그는 한 번만 남긴다
|
||||
|
||||
### 10.2 application service
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- application은 예외를 business/application 의미로 번역할 수 있다
|
||||
- 하지만 같은 예외를 무조건 ERROR로 남기지는 않는다
|
||||
- 최종 실패 책임이 상위 경계에 있으면 여기서는 DEBUG/WARN 맥락만 남길 수 있다
|
||||
|
||||
### 10.3 external client / integration adapter
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 외부 호출 1회 실패는 기본적으로 요약 로그
|
||||
- 재시도/fallback/최종 실패 여부에 따라 상위에서 대표 로그를 결정한다
|
||||
- 외부 payload/headers/token 원문은 남기지 않는다
|
||||
|
||||
### 10.4 advice / global exception handler
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청 실패의 대표 로그 위치를 advice로 정했다면 거기서 일관되게 남긴다
|
||||
- validation/4xx/5xx에 따라 레벨을 다르게 적용할 수 있다
|
||||
- advice가 응답 생성만 하고 로그는 요청 공통 경계에서 남기는 구조도 허용하되, 프로젝트 전체로 하나를 택한다
|
||||
|
||||
## 11. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- 같은 예외를 여러 레이어에서 모두 ERROR로 기록
|
||||
- log.error(ex.getMessage(), ex) 남발
|
||||
- request/response body 전문을 예외 로그에 포함
|
||||
- 토큰, 세션 ID, 비밀번호, 키, PII를 예외 로그에 원문으로 기록
|
||||
- validation/예상 가능한 4xx를 무조건 ERROR 처리
|
||||
- controller마다 try-catch + log.error + ResponseEntity 반복
|
||||
- stack trace 없이 맥락도 없는 ERROR 한 줄만 남김
|
||||
|
||||
## 12. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- 이 실패에 대해 대표 예외 로그가 한 번만 남는가?
|
||||
- 대표 로그는 사건 설명과 운영 키를 담는가?
|
||||
- 최종 실패만 ERROR로 남기고 있는가?
|
||||
- 복구된 예외를 과도하게 ERROR로 찍지 않는가?
|
||||
- 예외 로그에도 민감정보 마스킹 규칙이 그대로 적용되는가?
|
||||
- 로그 메시지와 API 응답 메시지를 분리하고 있는가?
|
||||
- stack trace가 필요한 곳에는 남고, 불필요한 중복은 줄였는가?
|
||||
@@ -0,0 +1,290 @@
|
||||
# Log Level 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 로그 레벨의 의미와 사용 기준을 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- 같은 종류의 사건이 서비스마다 다른 레벨로 찍히는 일을 줄인다
|
||||
- 운영에서 필요한 신호와 디버깅용 노이즈를 구분한다
|
||||
- 로그 레벨이 알림, 검색, 장애 대응에 일관되게 쓰이게 한다
|
||||
- ERROR/WARN/INFO/DEBUG/TRACE의 역할을 명확히 한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: Spring Boot / Spring Framework 공식 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 설명 위에 일반적인 운영 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 로그 레벨은 “중요도”와 “운영 행동 필요성”을 표현한다
|
||||
|
||||
Spring Boot는 기본 로그 출력에 level을 포함하고, observability를 logging, metrics, traces의 세 축으로 설명한다. 이 프로젝트에서 로그 레벨은 단순 출력 강도가 아니라 운영 의미를 표현한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 레벨은 “이 사건이 얼마나 심각한가”를 표현한다
|
||||
- 레벨은 “운영자가 지금 당장 봐야 하는가”를 암시한다
|
||||
- 디버깅 편의를 위해 레벨을 올려 쓰지 않는다
|
||||
|
||||
### 3.2 기본 운영 레벨은 INFO다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- production 기본 로그 레벨은 INFO
|
||||
- DEBUG/TRACE는 상시 기본값으로 열지 않는다
|
||||
- 장애 분석이나 특정 모듈 추적이 필요할 때 범위를 좁혀 일시적으로 올린다
|
||||
|
||||
이 규칙은 Spring Framework가 TRACE와 DEBUG를 과도한 fire hose가 되지 않게 다뤄야 한다고 설명하는 방향과도 맞다.
|
||||
|
||||
### 3.3 FATAL은 사용하지 않는다
|
||||
|
||||
Spring Boot 공식 문서는 Logback에는 FATAL 레벨이 없고 ERROR로 매핑된다고 명시한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- FATAL 레벨 사용 금지
|
||||
- 치명적 장애도 ERROR로 기록
|
||||
- “매우 치명적”이라는 구분은 레벨이 아니라 메시지, 에러 코드, 알림 정책으로 표현한다
|
||||
|
||||
## 4. 레벨별 기준
|
||||
|
||||
### 4.1 ERROR
|
||||
|
||||
의미
|
||||
|
||||
요청, 작업, 배치, 연동, 내부 처리 중 실패가 발생했고 정상 경로로 복구되지 않았음을 뜻한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청이 최종 실패한 경우
|
||||
- 외부 연동 실패가 호출 결과 실패로 이어진 경우
|
||||
- 비동기 작업/스케줄 작업이 실패로 종료된 경우
|
||||
- 복구되지 않은 예외가 발생한 경우
|
||||
- 데이터 정합성 문제, 시스템 오작동, 예상하지 못한 분기
|
||||
|
||||
이런 경우 ERROR
|
||||
|
||||
쓰지 말아야 할 경우
|
||||
|
||||
- 예외가 발생했지만 의도된 비즈니스 흐름인 경우
|
||||
- 재시도로 정상 복구된 중간 실패
|
||||
- validation 실패 같은 예상 가능한 클라이언트 오류를 서버 이상처럼 과장하는 경우
|
||||
|
||||
### 4.2 WARN
|
||||
|
||||
의미
|
||||
|
||||
즉시 실패는 아니지만 문제 가능성이 높거나, 운영자가 추후 확인할 가치가 있는 비정상 상황을 뜻한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 자동 복구되었지만 이상 징후가 있는 경우
|
||||
- fallback이 실행된 경우
|
||||
- 재시도 후 성공했지만 원인 추적이 필요한 경우
|
||||
- 느린 외부 의존성, 느린 health indicator, 비정상 입력 패턴
|
||||
- deprecation 사용, 설정 누락 대체값 적용, 비권장 경로 사용
|
||||
|
||||
이런 경우 WARN
|
||||
|
||||
Spring Boot는 느린 health indicator에 대해 기본적으로 warning 로그를 남기며, 그 임계값을 설정할 수 있다고 설명한다.
|
||||
|
||||
쓰지 말아야 할 경우
|
||||
|
||||
- 정상적인 분기
|
||||
- 자주 발생하는 business rejection
|
||||
- 운영상 행동이 전혀 필요 없는 정보성 사건
|
||||
|
||||
### 4.3 INFO
|
||||
|
||||
의미
|
||||
|
||||
서비스의 주요 정상 상태 변화와 운영상 의미 있는 사건을 뜻한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 애플리케이션 시작/종료
|
||||
- 중요한 배치 시작/완료
|
||||
- 의미 있는 상태 전이
|
||||
- 주요 외부 연동 시작/완료
|
||||
- 보안상 중요한 성공 이벤트
|
||||
- 운영자가 흐름을 이해하는 데 필요한 핵심 사건
|
||||
|
||||
이런 경우 INFO
|
||||
|
||||
쓰지 말아야 할 경우
|
||||
|
||||
- 모든 요청 진입/종료를 무조건 INFO로 남기는 것
|
||||
- 고빈도 내부 반복 처리
|
||||
- 디버깅용 변수 dump
|
||||
|
||||
### 4.4 DEBUG
|
||||
|
||||
의미
|
||||
|
||||
장애 분석이나 개발 중 원인 파악에 도움이 되는 상세 내부 흐름 정보다.
|
||||
|
||||
Spring Framework는 TRACE가 DEBUG와 유사한 원칙을 따르며 과도한 로그가 되어선 안 된다고 설명한다. 이 원칙은 DEBUG에도 그대로 적용된다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 주요 분기 결정 근거
|
||||
- 내부 상태 변화 상세
|
||||
- 외부 API 요청/응답 요약
|
||||
- 매핑 결과 요약
|
||||
- 재시도/백오프/fallback 판단 근거
|
||||
|
||||
이런 경우 DEBUG
|
||||
|
||||
쓰지 말아야 할 경우
|
||||
|
||||
- 민감정보 원문 출력
|
||||
- 너무 큰 payload 전체 dump
|
||||
- 운영 기본 레벨에서 계속 쌓이면 안 되는 고빈도 로그
|
||||
|
||||
### 4.5 TRACE
|
||||
|
||||
의미
|
||||
|
||||
매우 세밀한 흐름 추적용 로그다.
|
||||
|
||||
Spring Framework는 TRACE도 DEBUG처럼 다뤄야 하며 fire hose가 되어서는 안 된다고 설명한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 프레임워크/바인딩/직렬화/세밀한 분기 추적
|
||||
- 특정 문제 재현 시에만 필요한 상세 이벤트
|
||||
- 고빈도 루프/반복 처리의 초미세 추적
|
||||
|
||||
쓰지 말아야 할 경우
|
||||
|
||||
- production 상시 활성화
|
||||
- 요청 본문, 응답 본문, 대용량 객체 전체 출력
|
||||
- 민감정보 포함 가능성이 있는 상세 dump
|
||||
|
||||
## 5. 상황별 권장 기준
|
||||
|
||||
### 5.1 요청 처리
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 정상 요청 전체를 기본적으로 INFO로 남기지 않는다
|
||||
- 요청 실패는 최종 실패 시 ERROR
|
||||
- 비정상 입력 패턴을 별도 관찰할 필요가 있으면 WARN
|
||||
- 상세 요청 흐름은 DEBUG 이하
|
||||
|
||||
### 5.2 외부 API 연동
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 최종 실패: ERROR
|
||||
- 재시도 후 성공: 기본 WARN
|
||||
- fallback 성공: 기본 WARN
|
||||
- 단일 시도 상세 요청/응답 요약: DEBUG
|
||||
- payload 전문 출력: 기본 금지, 정말 필요하면 TRACE에서도 마스킹 필수
|
||||
|
||||
### 5.3 비즈니스 거절
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예상 가능한 비즈니스 거절은 기본적으로 INFO 또는 무로그
|
||||
- 같은 거절이 이상 징후를 뜻하면 WARN
|
||||
- 정상 정책 집행을 ERROR로 기록하지 않는다
|
||||
|
||||
예:
|
||||
|
||||
- 중복 가입 시도
|
||||
- 권한 없음
|
||||
- 유효성 검사 실패
|
||||
|
||||
이런 것은 기본적으로 서버 장애가 아니다
|
||||
|
||||
### 5.4 예외
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청/작업을 실패시키는 예외: ERROR
|
||||
- 재시도/대체 경로로 복구된 예외: WARN 또는 DEBUG
|
||||
- controller advice에서 이미 최종 실패를 기록했다면 하위 계층 중복 ERROR 로그를 피한다
|
||||
|
||||
## 6. 중복 로그 방지 규칙
|
||||
|
||||
### 6.1 같은 실패를 여러 레이어에서 모두 ERROR로 찍지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 한 실패에 대해 대표 ERROR 로그 한 번을 원칙으로 한다
|
||||
- 하위 계층은 필요하면 DEBUG/WARN으로 맥락만 남긴다
|
||||
- controller, service, client, advice가 같은 예외를 모두 stack trace와 함께 ERROR로 찍지 않는다
|
||||
|
||||
### 6.2 로깅 위치는 책임과 함께 정한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 최종 실패 책임을 가진 경계에서 대표 로그를 남긴다
|
||||
- 외부 연동 세부 맥락은 client/adapter에서 DEBUG/WARN
|
||||
- 공통 예외 응답 생성은 advice가 하더라도, 실제 대표 에러 로그 위치는 프로젝트 단위로 일관되게 정한다
|
||||
|
||||
## 7. 민감정보와 레벨의 관계
|
||||
|
||||
### 7.1 레벨이 낮다고 민감정보를 찍어도 되는 것은 아니다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- DEBUG/TRACE라도 비밀번호, 토큰, 주민번호, 카드번호, 이메일 전체값 등 민감정보 원문 출력 금지
|
||||
- 낮은 레벨은 더 상세할 수 있을 뿐, 보안 예외 구간이 아니다
|
||||
- 상세 추적이 필요하면 마스킹/요약/해시 처리한다
|
||||
|
||||
## 8. Correlation / Trace와의 관계
|
||||
|
||||
Spring Boot는 tracing이 활성화되면 로그에 correlation ID를 포함할 수 있다고 설명한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 레벨과 무관하게 운영 로그는 trace/correlation 연결이 가능해야 한다
|
||||
- 같은 오류가 여러 로그로 흩어져도 추적 가능한 키가 있어야 한다
|
||||
- traceId, principal, request path 기록 규칙은 별도 문서에서 구체화한다
|
||||
|
||||
## 9. 운영 설정 규칙
|
||||
|
||||
### 9.1 production 기본값
|
||||
|
||||
프로젝트 권장 기본값:
|
||||
|
||||
- root: INFO
|
||||
- 애플리케이션 주요 패키지: INFO
|
||||
- noisy framework package: 필요 시 WARN
|
||||
- 특정 문제 조사 시 모듈 단위로만 DEBUG/TRACE 상향
|
||||
|
||||
### 9.2 일시적 상향은 범위를 좁힌다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- root를 DEBUG/TRACE로 올리는 것을 기본 금지
|
||||
- 특정 패키지, 특정 client, 특정 기능 단위로만 조정
|
||||
- 문제 해결 후 원래 레벨로 되돌린다
|
||||
|
||||
## 10. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- FATAL 사용
|
||||
- 정상 흐름을 ERROR/WARN으로 과장
|
||||
- 실패 하나를 여러 레이어에서 중복 ERROR 출력
|
||||
- production 기본 레벨을 DEBUG/TRACE로 설정
|
||||
- DEBUG/TRACE에서 민감정보 원문 출력
|
||||
- 모든 요청/응답을 INFO로 남기는 것
|
||||
- 디버깅 편의 때문에 레벨 의미를 무너뜨리는 것
|
||||
|
||||
## 11. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- 이 로그는 해당 레벨의 운영 의미와 맞는가?
|
||||
- 정상 흐름을 불필요하게 경고/오류로 올리지 않았는가?
|
||||
- 같은 실패를 대표 로그 한 번으로 정리하고 있는가?
|
||||
- DEBUG/TRACE가 상시 fire hose가 되지 않는가?
|
||||
- 민감정보가 레벨과 무관하게 보호되는가?
|
||||
- tracing/correlation과 연결 가능한가?
|
||||
@@ -0,0 +1,359 @@
|
||||
# Log Message Format 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 로그 메시지의 형식과 구성 원칙을 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- 로그가 사람과 시스템 모두에게 읽기 쉬운 형식을 갖게 한다
|
||||
- 검색, 집계, 상관 분석에 필요한 키를 일관되게 남긴다
|
||||
- free text만으로 의미를 전달하는 로그를 줄인다
|
||||
- 운영 로그와 디버깅 로그의 메시지 품질을 일정 수준 이상으로 유지한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: Spring Boot 공식 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 기능 위에 일반적인 운영 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 로그 메시지는 “짧은 사건 설명 + 핵심 키-값” 구조를 기본으로 한다
|
||||
|
||||
Spring Boot 기본 로그는 timestamp, level, thread, correlation ID, logger, message 같은 요소를 이미 분리해서 출력한다. 이 프로젝트에서는 message 본문도 이와 같은 방향으로 짧은 사건 설명 + 핵심 필드 구조를 따르도록 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 메시지 앞부분은 한 문장 사건 설명
|
||||
- 뒤에는 검색 가능한 핵심 필드를 key-value로 붙인다
|
||||
- 긴 문장 서술형 로그보다 구조화된 짧은 메시지를 선호한다
|
||||
|
||||
권장 예:
|
||||
|
||||
```text
|
||||
Failed to issue external auth token. provider=keycloak actorId=123 requestPath=/api/v1/sessions
|
||||
```
|
||||
|
||||
### 3.2 free text보다 필드가 더 중요하다
|
||||
|
||||
Spring Boot는 구조화 로그를 공식 지원하고, JSON 필드 include/exclude/rename/add까지 제공한다. 이는 운영에서 로그를 사람이 읽기만 하는 것이 아니라 시스템이 수집·검색·집계한다는 뜻이다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 메시지에 사건 설명은 필요하지만, 분석용 핵심 정보는 필드 형태로 남긴다
|
||||
- 같은 종류의 로그는 가능한 한 같은 키 이름을 쓴다
|
||||
- 검색/집계 가능한 키를 free text 속에만 숨기지 않는다
|
||||
|
||||
### 3.3 로그 형식은 전역적으로 일관되어야 한다
|
||||
|
||||
Spring Boot는 기본 포맷과 구조화 포맷을 전역 설정으로 관리할 수 있다. 이 프로젝트도 메시지 형식을 클래스마다 제각각 두지 않는다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 같은 종류의 사건은 같은 키 이름과 같은 순서를 최대한 유지한다
|
||||
- 운영 로그 형식은 팀 공통 규약으로 다룬다
|
||||
- 특정 개발자 취향에 따라 메시지 문체가 바뀌지 않게 한다
|
||||
|
||||
## 4. 기본 메시지 형식
|
||||
|
||||
### 4.1 권장 기본 형식
|
||||
|
||||
권장 형식:
|
||||
|
||||
```text
|
||||
<짧은 사건 설명>. key1=value1 key2=value2 key3=value3
|
||||
```
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 사건 설명은 과도하게 길지 않게 쓴다
|
||||
- 사건 설명 뒤에 핵심 필드를 공백으로 구분해 이어 붙인다
|
||||
- 문장 속에 값을 길게 섞어 넣기보다 key=value 형태를 우선한다
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
User registration completed. actorId=123 userId=u_001
|
||||
External auth request failed. provider=keycloak actorId=123 status=503
|
||||
```
|
||||
|
||||
### 4.2 메시지는 과거형/완료형보다 사건 중심으로 쓴다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- “무슨 일이 일어났는가”가 바로 보이게 쓴다
|
||||
- 장황한 설명보다 사건명 중심으로 쓴다
|
||||
- 성공/실패/재시도/대체 경로가 제목 수준에서 드러나야 한다
|
||||
|
||||
권장:
|
||||
|
||||
- Created session
|
||||
- Failed to create session
|
||||
- Retried external auth request
|
||||
- Applied fallback token validation
|
||||
|
||||
비권장:
|
||||
|
||||
- Trying to do session creation and got an unexpected issue while processing
|
||||
|
||||
## 5. 필수/권장 필드 규칙
|
||||
|
||||
### 5.1 운영 핵심 로그의 필수 후보 필드
|
||||
|
||||
Spring Boot 기본 로그에는 이미 thread, logger, correlation ID 같은 정보가 들어갈 수 있고, tracing이 활성화되면 correlation ID도 로그에 포함된다. 이 프로젝트는 메시지 본문에서도 운영에 필요한 business key를 추가로 남긴다.
|
||||
|
||||
운영 핵심 로그의 권장 필드:
|
||||
|
||||
- traceId 또는 correlation ID 연결 가능 정보
|
||||
- actorId 또는 principal 식별자
|
||||
- requestPath
|
||||
- operation
|
||||
- resourceId
|
||||
- externalSystem
|
||||
- status 또는 errorCode
|
||||
- durationMs
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 모든 로그에 다 넣으라는 뜻은 아니다
|
||||
- 그 사건을 운영에서 추적하는 데 필요한 최소 필드를 고른다
|
||||
- 메시지마다 필드 이름을 바꾸지 않는다
|
||||
|
||||
### 5.2 같은 의미에는 같은 키 이름을 쓴다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 사용자 식별자는 userId 또는 actorId 중 하나로 표준화한다
|
||||
- 경로는 path가 아니라 requestPath처럼 의미를 분명히 한다
|
||||
- 외부 연동 대상은 provider, externalSystem, clientName 중 문서로 정한 하나를 사용한다
|
||||
|
||||
예:
|
||||
|
||||
- userId, uid, memberId를 섞지 않는다
|
||||
- url, uri, path를 상황마다 바꾸지 않는다
|
||||
|
||||
## 6. 사람이 읽는 로그와 구조화 로그의 관계
|
||||
|
||||
### 6.1 기본 텍스트 로그도 구조화 가능해야 한다
|
||||
|
||||
Spring Boot는 콘솔/파일 로그를 기본 텍스트 형식으로 출력하면서도, correlation ID와 핵심 정보를 포함할 수 있게 설계되어 있다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 텍스트 로그라도 key=value 패턴을 유지한다
|
||||
- grep/search가 가능해야 한다
|
||||
- “말이 되는 문장”보다 “검색 가능한 문장”을 우선한다
|
||||
|
||||
### 6.2 구조화 로그(JSON)는 수집 시스템이 있으면 우선 검토한다
|
||||
|
||||
Spring Boot는 ECS, GELF, Logstash JSON structured logging을 공식 지원한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 중앙 수집 시스템(예: ELK, Graylog, Datadog 등)이 있으면 structured logging을 우선 검토한다
|
||||
- 다만 JSON 로그를 쓰더라도 필드 naming 규칙과 메시지 사건 설명 규칙은 그대로 유지한다
|
||||
- 텍스트 로그와 JSON 로그가 서로 전혀 다른 의미 체계를 가지지 않게 한다
|
||||
|
||||
### 6.3 JSON 구조는 ingestion 시스템에 맞추되, 프로젝트 핵심 필드는 유지한다
|
||||
|
||||
Spring Boot는 JSON structured logging에서 include/exclude/rename/add를 지원한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 수집 시스템 요구에 맞게 JSON 필드명을 조정할 수 있다
|
||||
- 그러나 프로젝트 핵심 검색 키(traceId, actorId, requestPath, errorCode)는 일관되게 유지한다
|
||||
- ingestion 편의 때문에 business 의미가 흐려지지 않게 한다
|
||||
|
||||
## 7. 예외와 스택트레이스 메시지 규칙
|
||||
|
||||
### 7.1 메시지와 예외 스택트레이스는 역할이 다르다
|
||||
|
||||
Spring Boot는 구조화 로그에서 예외가 함께 로그되면 stack trace도 포함되며, 비용을 줄이기 위해 출력 방식을 조정할 수 있다고 설명한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 메시지는 “무슨 요청/작업이 왜 실패했는지”를 요약한다
|
||||
- stack trace는 원인 분석용이다
|
||||
- stack trace가 있으니 메시지를 대충 쓰지 않는다
|
||||
- 반대로 메시지가 충분하다고 stack trace를 무조건 생략하지도 않는다
|
||||
|
||||
### 7.2 예외 메시지를 그대로 로그 제목으로 쓰지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- log.error(ex.getMessage(), ex) 형태를 기본값으로 쓰지 않는다
|
||||
- 사건 설명과 운영 키를 먼저 쓰고 예외를 마지막 인자로 붙인다
|
||||
- 예외 메시지는 보조 정보이지, 로그 제목의 전부가 아니다
|
||||
|
||||
권장:
|
||||
|
||||
```java
|
||||
log.error("Failed to issue token. provider={} actorId={}", provider, actorId, ex);
|
||||
```
|
||||
|
||||
비권장:
|
||||
|
||||
```java
|
||||
log.error(ex.getMessage(), ex);
|
||||
```
|
||||
|
||||
## 8. 민감정보/대용량 데이터 규칙
|
||||
|
||||
### 8.1 메시지 본문에 민감정보 원문을 넣지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 비밀번호, 액세스 토큰, 리프레시 토큰, 인증 헤더, 주민번호, 카드번호, 이메일 전체값 등은 원문 출력 금지
|
||||
- 필요한 경우 일부 마스킹, 해시, 길이/유형 정보만 출력
|
||||
- 구조화 로그에서도 같은 기준을 적용한다
|
||||
|
||||
### 8.2 payload 전문을 기본 로그 메시지에 넣지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- request/response 전문 출력은 기본 금지
|
||||
- 꼭 필요하면 별도 debug/trace 전용 경로에서 제한적으로 출력
|
||||
- 긴 배열, JSON 본문, 바이너리 응답, HTML 전체를 로그 메시지에 직접 넣지 않는다
|
||||
|
||||
### 8.3 식별자는 최소한으로 남긴다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 사람을 직접 식별하는 값보다 내부 식별자(userId, sessionId, orderId)를 우선 사용
|
||||
- 외부 식별자가 필요해도 전체 원문 대신 일부만 남기는 방식을 검토한다
|
||||
|
||||
## 9. 메시지 작성 세부 규칙
|
||||
|
||||
### 9.1 시제와 문체를 통일한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 사건 제목은 영어 기준 단순 과거/완료형 또는 failed/succeeded 형식으로 통일한다
|
||||
- 같은 팀 안에서 create user success, user created, successfully created user처럼 문체가 섞이지 않게 한다
|
||||
- 문장 종결 부호는 짧게 유지한다
|
||||
|
||||
권장 예:
|
||||
|
||||
- Created user
|
||||
- Failed to create user
|
||||
- Completed session cleanup
|
||||
- Rejected invalid request
|
||||
|
||||
### 9.2 불필요한 수식어를 줄인다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- very, really, unexpectedly, seriously 같은 감정/강조 표현 지양
|
||||
- 심각도는 레벨과 에러 코드가 표현하게 한다
|
||||
- 메시지는 사실 중심으로 작성한다
|
||||
|
||||
### 9.3 단위가 있는 값은 키 이름에 단위를 포함한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 시간은 durationMs
|
||||
- 바이트는 payloadBytes
|
||||
- 개수는 itemCount
|
||||
- 단위를 메시지 문장 속에 숨기지 않는다
|
||||
|
||||
## 10. 권장 메시지 패턴
|
||||
|
||||
### 10.1 요청 처리
|
||||
|
||||
성공:
|
||||
|
||||
```text
|
||||
Completed request. requestPath=/api/v1/users method=POST status=201 durationMs=42
|
||||
```
|
||||
|
||||
실패:
|
||||
|
||||
```text
|
||||
Failed request. requestPath=/api/v1/users method=POST status=500 errorCode=INTERNAL_SERVER_ERROR durationMs=42
|
||||
```
|
||||
|
||||
### 10.2 외부 연동
|
||||
|
||||
성공:
|
||||
|
||||
```text
|
||||
Completed external auth request. provider=keycloak status=200 durationMs=84
|
||||
```
|
||||
|
||||
재시도 후 성공:
|
||||
|
||||
```text
|
||||
Succeeded external auth request after retry. provider=keycloak attempts=2 durationMs=312
|
||||
```
|
||||
|
||||
실패:
|
||||
|
||||
```text
|
||||
Failed external auth request. provider=keycloak status=503 errorCode=UPSTREAM_AUTH_SERVER_UNAVAILABLE
|
||||
```
|
||||
|
||||
### 10.3 배치/스케줄
|
||||
|
||||
시작:
|
||||
|
||||
```text
|
||||
Started expired session cleanup. job=expired-session-cleanup
|
||||
```
|
||||
|
||||
완료:
|
||||
|
||||
```text
|
||||
Completed expired session cleanup. job=expired-session-cleanup deletedCount=143 durationMs=1820
|
||||
```
|
||||
|
||||
실패:
|
||||
|
||||
```text
|
||||
Failed expired session cleanup. job=expired-session-cleanup durationMs=905
|
||||
```
|
||||
|
||||
## 11. 구현 규칙
|
||||
|
||||
### 11.1 logger name에 의미를 실지 말고 메시지에 실는다
|
||||
|
||||
Spring Boot 기본 포맷은 logger name을 별도로 출력한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- logger name은 클래스/패키지 출처를 나타내는 데 충분하다
|
||||
- 사건 의미는 메시지와 key-value 필드에 둔다
|
||||
- logger 이름 자체를 읽어야만 사건을 이해할 수 있게 만들지 않는다
|
||||
|
||||
### 11.2 MDC/trace와 겹치는 필드는 중복을 줄인다
|
||||
|
||||
Spring Boot tracing은 correlation ID를 로그에 포함할 수 있고, 패턴 커스터마이징도 지원한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 이미 공통 패턴에 있는 값(traceId 등)을 메시지에 또 반복하지 않는다
|
||||
- 다만 수집 시스템이나 검색 UX상 필요한 경우만 선택적으로 중복한다
|
||||
- 메시지 필드와 로그 패턴 필드의 책임을 나눈다
|
||||
|
||||
## 12. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- free text만 길게 쓰고 검색 가능한 키를 남기지 않음
|
||||
- 예외 메시지 자체를 로그 제목으로 사용
|
||||
- 같은 의미의 키 이름을 로그마다 다르게 사용
|
||||
- request/response payload 전문을 기본 로그에 출력
|
||||
- 민감정보 원문 출력
|
||||
- structured logging을 쓰면서도 JSON 필드 의미가 일관되지 않음
|
||||
- traceId, actorId, requestPath 같은 핵심 필드를 상황마다 제멋대로 이름 붙임
|
||||
|
||||
## 13. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- 이 메시지는 한 줄 사건 설명으로 빠르게 이해 가능한가?
|
||||
- 운영에 필요한 핵심 값이 key=value 형태로 드러나는가?
|
||||
- 같은 종류의 로그와 키 이름/순서/문체가 일관적인가?
|
||||
- 메시지와 stack trace의 역할이 분리되어 있는가?
|
||||
- 민감정보나 대용량 payload가 원문으로 들어가지 않았는가?
|
||||
- structured logging으로 전환해도 의미가 유지되는 형식인가?
|
||||
@@ -0,0 +1,260 @@
|
||||
# Operation Indicator / Health Check 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 운영 상태 노출과 health check 기준을 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- health endpoint를 단순 ping이 아니라 운영 계약으로 다룬다
|
||||
- liveness, readiness, startup의 의미를 구분한다
|
||||
- 외부 의존성 포함 여부를 일관되게 결정한다
|
||||
- Kubernetes probe, Actuator health group, custom health indicator의 역할을 분리한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: Spring Boot / Kubernetes 공식 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 기능 위에 일반적인 운영 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 health check는 “살아 있음”과 “트래픽 수용 가능”을 구분해야 한다
|
||||
|
||||
Kubernetes는 probe를 startup, liveness, readiness 세 종류로 구분합니다. liveness는 컨테이너를 재시작해야 하는지 판단하고, readiness는 현재 트래픽을 받을 준비가 되었는지 판단하며, startup은 느린 시작 동안 liveness/readiness 실행을 지연시키는 역할을 합니다. Spring Boot도 ApplicationAvailability를 통해 liveness와 readiness 상태를 별도로 다루고, 이를 health group으로 노출합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- health check를 하나의 “UP/DOWN” 개념으로만 보지 않는다
|
||||
- liveness 와 readiness 를 분리한다
|
||||
- startup 시간이 긴 서비스는 startup probe 검토를 기본으로 한다
|
||||
|
||||
### 3.2 health endpoint는 운영자와 오케스트레이터를 위한 계약이다
|
||||
|
||||
Spring Boot Actuator의 health endpoint는 운영과 모니터링을 위한 엔드포인트이며, health group, status severity order, HTTP status mapping, show-details 정책 등을 설정할 수 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- health endpoint는 애플리케이션 내부 디버그 API가 아니다
|
||||
- 외부 공개 API처럼 임의 shape를 만들지 않는다
|
||||
- Actuator health 규약 위에 필요한 최소 custom indicator만 추가한다
|
||||
|
||||
### 3.3 health는 “무엇을 자동화할 것인가”를 기준으로 설계한다
|
||||
|
||||
Kubernetes는 liveness 실패 시 컨테이너를 재시작하고, readiness 실패 시 그 인스턴스로 트래픽을 보내지 않습니다. 따라서 어떤 검사를 어디에 넣을지는 “실패했을 때 플랫폼이 어떤 행동을 해도 안전한가”를 기준으로 정해야 합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 재시작이 정답인 문제만 liveness에 반영한다
|
||||
- 일시적 과부하/의존성 문제/초기화 대기는 readiness에 반영할지 검토한다
|
||||
- 단순 상태 조회와 자동 운영 액션 유발 신호를 혼동하지 않는다
|
||||
|
||||
## 4. 엔드포인트 표준
|
||||
|
||||
### 4.1 기본 health endpoint는 /actuator/health다
|
||||
|
||||
Spring Boot는 기본적으로 health endpoint를 제공하고, 일반적으로 /actuator/health에 매핑합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 기본 health endpoint는 Actuator 기본 경로를 따른다
|
||||
- 커스텀 /health를 별도로 만드는 것을 기본값으로 두지 않는다
|
||||
- 외부 노출 정책은 actuator exposure/security 정책과 함께 설계한다
|
||||
|
||||
### 4.2 Kubernetes probe는 health group 경로를 사용한다
|
||||
|
||||
Spring Boot는 Kubernetes 환경에서 /actuator/health/liveness 와 /actuator/health/readiness 를 별도 HTTP probe로 노출할 수 있고, management.endpoint.health.probes.enabled로 제어할 수 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
Kubernetes probe는 기본적으로
|
||||
|
||||
- liveness: /actuator/health/liveness
|
||||
- readiness: /actuator/health/readiness
|
||||
- 별도 이유가 없으면 커스텀 probe URL을 새로 만들지 않는다
|
||||
|
||||
### 4.3 관리 포트 분리 시 메인 포트 추가 노출을 검토한다
|
||||
|
||||
Spring Boot는 actuator가 별도 management port에만 있으면 실제 애플리케이션 연결 상태와 probe 결과가 어긋날 수 있다고 설명하고, management.endpoint.health.probes.add-additional-paths=true로 메인 포트에 /livez, /readyz를 추가할 수 있다고 안내합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- management port를 분리한 서비스는 메인 포트에도 probe path를 노출할지 기본 검토한다
|
||||
- Kubernetes 운영이면 /livez, /readyz 추가 노출을 우선 검토한다
|
||||
- “actuator만 살아 있고 앱은 실제로 못 받는” false positive를 피한다
|
||||
|
||||
## 5. Liveness 표준
|
||||
|
||||
### 5.1 liveness는 “재시작이 필요한가”를 판단한다
|
||||
|
||||
Kubernetes는 liveness probe를 컨테이너 재시작 판단에 사용하고, Spring Boot는 liveness를 “애플리케이션이 스스로 회복 가능한가, 아니면 broken state라 재시작이 필요한가”의 의미로 설명합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- liveness는 프로세스/애플리케이션 내부가 더 이상 정상 진행 불가한가 를 표현한다
|
||||
- deadlock, 내부 상태 붕괴, 회복 불가능한 오류는 liveness 대상이 될 수 있다
|
||||
- 일시적 외부 의존성 장애는 기본적으로 liveness 대상이 아니다
|
||||
|
||||
### 5.2 liveness는 외부 시스템 의존을 기본 금지한다
|
||||
|
||||
Spring Boot는 liveness 상태를 DB, 외부 Web API, 외부 캐시 같은 외부 체크에 기반하면 대규모 재시작과 cascading failure를 유발할 수 있으므로 일반적으로 그렇게 하지 말라고 설명합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- DB 연결 상태를 liveness에 넣지 않는다
|
||||
- 외부 API 상태를 liveness에 넣지 않는다
|
||||
- Redis, Kafka, S3 같은 외부 의존성도 기본적으로 liveness에 넣지 않는다
|
||||
|
||||
## 6. Readiness 표준
|
||||
|
||||
### 6.1 readiness는 “현재 트래픽을 받을 준비가 되었는가”를 판단한다
|
||||
|
||||
Kubernetes는 readiness probe를 트래픽 수용 가능 여부 판단에 사용하고, 초기 연결 수립, 파일 로딩, 캐시 warming, 일시적 과부하 복구 같은 상황에 유용하다고 설명합니다. Spring Boot도 readiness 상태를 별도 availability state로 다룹니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- readiness는 현재 인스턴스가 요청을 받아도 되는가 를 표현한다
|
||||
- 시작 중, 일시적 overload, 내부 준비 미완료는 readiness false 대상이 될 수 있다
|
||||
- readiness 실패는 기본적으로 “트래픽 제외” 의미다
|
||||
|
||||
### 6.2 readiness의 외부 의존성 포함은 신중하게 결정한다
|
||||
|
||||
Spring Boot는 readiness probe에 외부 체크를 넣을지 여부는 개발자가 신중히 판단해야 하며, 기본적으로 추가 health check를 넣지 않는다고 설명합니다. 공유된 외부 시스템을 readiness에 넣으면 전체 서비스가 한꺼번에 서비스 제외될 수 있고, 반대로 fallback/circuit breaker가 있는 비필수 의존성은 readiness에 넣지 말아야 한다고도 설명합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
readiness에 외부 의존성을 넣기 전에 아래를 판단한다
|
||||
|
||||
- 이 의존성이 필수 인가
|
||||
- 장애 시 이 인스턴스만 제외하는 것이 맞는가
|
||||
- fallback/circuit breaker로 계속 서비스 가능 한가
|
||||
- 전체 인스턴스가 동시에 readiness false가 될 위험은 없는가
|
||||
- 비필수 외부 시스템은 readiness에서 제외한다
|
||||
- fallback 가능한 외부 시스템은 readiness에서 제외하는 쪽을 기본 선호한다
|
||||
|
||||
## 7. Startup probe 표준
|
||||
|
||||
### 7.1 startup probe는 느린 시작을 보호할 때 사용한다
|
||||
|
||||
Kubernetes는 startup probe가 있으면 그것이 성공하기 전까지 liveness/readiness를 실행하지 않는다고 설명합니다. Spring Boot도 애플리케이션이 liveness 기간보다 오래 걸려 시작할 수 있으면 startup probe를 가능한 해결책으로 언급합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 시작 시간이 긴 서비스는 startup probe를 기본 검토한다
|
||||
- migration, warm-up, 대규모 cache load, 대형 model load 같은 작업이 있으면 startup probe를 우선 검토한다
|
||||
- startup probe 없이 liveness 초기 threshold만 무작정 늘리는 방식을 기본값으로 두지 않는다
|
||||
|
||||
### 7.2 readiness만으로 충분한 경우도 있다
|
||||
|
||||
Spring Boot는 일반적으로 readiness probe가 시작 완료 전까지 실패하므로, startup probe가 항상 필요한 것은 아니라고 설명합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- startup probe는 필수 기본값 이 아니다
|
||||
- readiness만으로 충분하면 추가하지 않는다
|
||||
- startup probe는 “느린 시작 때문에 liveness가 오탐하는 경우”에 우선 도입한다
|
||||
|
||||
## 8. Health indicator 표준
|
||||
|
||||
### 8.1 indicator는 “운영상 의미 있는 상태”만 검사한다
|
||||
|
||||
Spring Boot Actuator는 built-in health endpoint와 health group을 제공하고, health group에 include/exclude를 둘 수 있으며, 존재하지 않는 contributor를 참조하면 기본적으로 startup validation에 실패하도록 할 수 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- custom health indicator는 운영상 실제 의미가 있을 때만 추가한다
|
||||
- “검사할 수 있으니 넣는다”를 금지한다
|
||||
- health indicator가 너무 많아져 endpoint가 내부 진단 덤프가 되지 않게 한다
|
||||
|
||||
### 8.2 느린 health indicator는 운영 비용으로 본다
|
||||
|
||||
Spring Boot는 느린 health indicator에 대해 warning 로그를 남기는 threshold 설정을 제공하며 기본값은 10초입니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- health check는 저비용이어야 한다
|
||||
- 느린 indicator는 원인 분석 대상이다
|
||||
- health endpoint가 무거운 DB query나 외부 API full round-trip을 기본 수행하지 않게 한다
|
||||
|
||||
### 8.3 health group은 의미 단위로 나눈다
|
||||
|
||||
Spring Boot는 health endpoint groups를 지원하고, liveness/readiness도 그 위에 구현되어 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
기본 그룹은
|
||||
|
||||
- global health
|
||||
- liveness
|
||||
- readiness
|
||||
|
||||
별도 그룹이 필요하면 운영 목적이 분명할 때만 만든다
|
||||
|
||||
그룹 membership은 존재하지 않는 indicator 참조 없이 명시적으로 관리한다
|
||||
|
||||
## 9. 응답/노출 규칙
|
||||
|
||||
### 9.1 health detail 노출은 최소화한다
|
||||
|
||||
Spring Boot는 management.endpoint.health.show-details와 show-components로 상세 노출 범위를 제어할 수 있고, 기본 show-details는 never입니다. 또한 health endpoint 접근과 세부 노출 권한도 제어할 수 있습니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 외부/일반 접근에서는 상세 health 정보를 기본 비노출
|
||||
- 운영자/내부 접근에서만 상세 정보 노출을 검토한다
|
||||
- health endpoint를 내부 시스템 구조 설명 API처럼 만들지 않는다
|
||||
|
||||
### 9.2 health status와 HTTP status mapping은 임의 변경을 지양한다
|
||||
|
||||
Spring Boot는 health status를 HTTP status로 매핑하는 설정을 제공하고, 기본 등록된 status는 sensible default로 매핑된다고 설명합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 기본 health status → HTTP status 매핑을 우선 유지한다
|
||||
- 특별한 운영 이유가 없으면 custom mapping을 남발하지 않는다
|
||||
- health endpoint HTTP status는 probe와 모니터링이 기대하는 의미를 깨지 않게 한다
|
||||
|
||||
## 10. 구현 위치 규칙
|
||||
|
||||
### 10.1 probe 경로는 controller가 아니라 Actuator가 담당한다
|
||||
|
||||
Spring Boot는 health endpoint, health group, liveness/readiness probe를 actuator로 제공하므로, probe 용 controller를 별도로 만드는 것이 기본 설계가 아닙니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- /actuator/health* 계열은 actuator에 맡긴다
|
||||
- @RestController("/health") 같은 수제 구현을 기본 금지한다
|
||||
- 필요한 확장은 custom HealthIndicator나 health group 설정으로 해결한다
|
||||
|
||||
### 10.2 외부 의존성 상태 판단은 integration 경계에서, 최종 반영은 health group에서 한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 외부 시스템 상태 판단 로직은 관련 integration component가 책임질 수 있다
|
||||
- 하지만 probe 포함 여부는 readiness/liveness 정책 문맥에서 최종 결정한다
|
||||
- 연동 로직이 있다고 해서 자동으로 health indicator에 포함하지 않는다
|
||||
|
||||
## 11. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- liveness에 DB/외부 API/캐시 같은 외부 의존성 기본 포함
|
||||
- readiness에 모든 외부 시스템을 무비판적으로 포함
|
||||
- 느린/무거운 쿼리를 health indicator에 넣음
|
||||
- probe 용 endpoint를 controller로 직접 새로 만듦
|
||||
- management 포트 분리 상태에서 메인 앱 수용성 차이를 무시
|
||||
- startup이 긴 서비스에 startup probe 검토 없이 liveness 오탐을 방치
|
||||
- health detail을 외부에 과도하게 노출
|
||||
- health group에 존재하지 않는 indicator를 느슨하게 참조
|
||||
|
||||
## 12. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- liveness와 readiness의 의미를 분리했는가?
|
||||
- liveness가 외부 의존성 상태에 흔들리지 않는가?
|
||||
- readiness에 포함한 외부 의존성은 정말 필수인가?
|
||||
- startup이 긴 서비스라면 startup probe를 검토했는가?
|
||||
- health indicator가 저비용인가?
|
||||
- management 포트 분리 시 main port 추가 probe 경로를 검토했는가?
|
||||
- health detail 노출 범위를 최소화했는가?
|
||||
@@ -0,0 +1,305 @@
|
||||
# PII Masking 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 로그, 트레이스, 운영 이벤트 기록에서 개인정보(PII)와 민감정보를 어떻게 마스킹하거나 제거할지 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- 운영에 필요한 관측 가능성을 유지하면서 민감정보 노출을 막는다
|
||||
- 로그 레벨과 무관하게 보호해야 하는 데이터를 명확히 한다
|
||||
- 마스킹 책임을 개별 개발자 습관이 아니라 공통 규칙으로 만든다
|
||||
- request/response, exception, external API payload, structured logging 모두에 같은 기준을 적용한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: OWASP, Spring Boot 공식 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 가이드 위에 일반적인 운영/보안 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 민감정보 보호는 예외가 아니라 기본값이다
|
||||
|
||||
OWASP는 세션 식별값, 액세스 토큰, 민감한 개인정보, 비밀번호, DB 연결 문자열, 암호화 키, 결제 데이터 등은 로그에 직접 기록하지 말고 제거·마스킹·정제·해시·암호화하라고 권고합니다. 따라서 이 프로젝트에서는 “필요할 때만 숨긴다”가 아니라 기본적으로 숨기고, 꼭 필요한 최소 정보만 남긴다를 원칙으로 둔다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 민감정보는 기본 비기록(non-log) 이 원칙
|
||||
- 필요하면 원문 대신 마스킹/해시/요약값을 남긴다
|
||||
- “디버깅 중이라서”, “DEBUG니까”, “내부망이라서” 같은 이유로 예외를 만들지 않는다
|
||||
|
||||
### 3.2 로그 레벨이 낮다고 민감정보를 찍어도 되는 것은 아니다
|
||||
|
||||
OWASP는 민감정보 자체를 직접 기록하지 말라고 하며, 이는 특정 로그 레벨에 한정된 예외를 두지 않습니다. 이 프로젝트도 DEBUG/TRACE를 보안 예외 구간으로 취급하지 않는다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- INFO, DEBUG, TRACE 모두 같은 마스킹 규칙을 따른다
|
||||
- 낮은 레벨은 더 자세할 수 있을 뿐, 더 위험한 데이터를 허용하는 레벨이 아니다
|
||||
|
||||
### 3.3 민감정보 마스킹은 로깅 호출부와 공통 로깅 파이프라인이 함께 책임진다
|
||||
|
||||
Spring Boot는 MDC를 로그 패턴과 structured logging JSON에 포함할 수 있고, JSON 필드의 include/exclude/rename/add와 customizer도 지원합니다. 따라서 민감정보 보호는 “개발자가 매번 조심해서 안 찍는 것”만으로 끝내지 않고, 공통 로깅 구성을 통한 2차 방어선도 둘 수 있다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 1차 방어: 애플리케이션 코드에서 원문을 로그 인자로 넘기지 않는다
|
||||
- 2차 방어: 공통 로깅 설정/structured logging customizer/appender 등에서 추가 마스킹을 검토한다
|
||||
- 둘 중 하나만 믿지 않는다
|
||||
|
||||
## 4. 데이터 분류 규칙
|
||||
|
||||
### 4.1 절대 원문 기록 금지 데이터
|
||||
|
||||
OWASP 기준으로 직접 기록하지 말아야 할 대표 데이터는 다음과 같습니다. 세션 식별값, 액세스 토큰, 민감한 개인정보와 일부 PII, 인증 비밀번호, DB 연결 문자열, 암호화 키 및 주요 비밀값, 은행 계좌/카드 정보 등이 여기에 해당합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
다음은 원문 로그 금지 다.
|
||||
|
||||
- 비밀번호
|
||||
- access token / refresh token / bearer token
|
||||
- session id / cookie session value / JWT raw token
|
||||
- DB connection string
|
||||
- encryption key / signing key / secret key / API secret
|
||||
- 카드번호 / 계좌번호 / 결제 식별정보 원문
|
||||
- 주민등록번호/여권번호/정부 식별번호류
|
||||
- 건강정보, 법적으로 민감한 개인정보
|
||||
- 외부 시스템 인증 헤더 원문
|
||||
- source code, stack dump 속 비밀 설정값
|
||||
|
||||
### 4.2 기본적으로 직접 기록하지 않는 데이터
|
||||
|
||||
OWASP는 민감한 개인정보 외에도 personal names, telephone numbers, email addresses, internal network names/addresses, file paths 등은 특별한 취급이 필요할 수 있다고 설명합니다. 이 프로젝트에서는 다음 데이터도 원문 기록을 기본값으로 두지 않는다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
다음은 원문 비권장 이며, 필요 시 축약/부분 마스킹/내부 식별자 대체를 우선한다.
|
||||
|
||||
- 이메일 전체값
|
||||
- 전화번호 전체값
|
||||
- 개인 이름 전체값
|
||||
- 내부 IP/호스트명/내부 네트워크 이름
|
||||
- 로컬 파일 경로
|
||||
- 상세 주소
|
||||
- 주민등록번호 일부를 유추할 수 있는 조합값
|
||||
|
||||
### 4.3 운영에 필요한 식별자는 내부 식별자를 우선한다
|
||||
|
||||
OWASP는 direct/indirect identifier에 대해 de-identification을 검토하라고 권고합니다. 이 프로젝트에서는 사람이 직접 식별되는 값보다 내부 식별자를 우선 남기는 방향을 기본으로 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- email 대신 userId
|
||||
- accountNumber 대신 accountId
|
||||
- phoneNumber 대신 customerId
|
||||
- 꼭 외부 식별자가 필요하면 일부만 남긴다
|
||||
|
||||
## 5. 처리 방식 규칙
|
||||
|
||||
### 5.1 제거, 마스킹, 해시, 암호화의 기본 선택
|
||||
|
||||
OWASP는 민감값을 제거, 마스킹, 정제, 해시, 암호화 중 적절한 방식으로 처리하라고 설명합니다. 이 프로젝트는 다음 우선순위를 권장한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 가장 먼저 검토: 아예 기록하지 않기
|
||||
- 운영 식별이 필요: 부분 마스킹
|
||||
- 동일 값 상관관계만 필요: 해시/토큰화
|
||||
- 정말 복구 가능한 보관이 필요: 별도 보호 저장소를 검토하고 일반 애플리케이션 로그에는 두지 않는다
|
||||
|
||||
### 5.2 부분 마스킹 규칙
|
||||
|
||||
프로젝트 권장 예:
|
||||
|
||||
- 이메일: do***@example.com
|
||||
- 전화번호: 010-****-1234
|
||||
- 카드번호: ************1234
|
||||
- 계좌번호: ******7890
|
||||
- 주민번호류: 기본 로그 금지, 부분 마스킹도 최소화
|
||||
- 토큰/세션 ID: 원문 금지, 필요하면 앞/뒤 일부 + 해시 일부
|
||||
|
||||
### 5.3 해시 사용 규칙
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 같은 값의 반복 여부만 추적하면 될 때 해시 사용 가능
|
||||
- salt/secret이 필요한 경우 보안 정책을 따른다
|
||||
- 해시값도 장기 식별자로 과도하게 남용하지 않는다
|
||||
- 원문 복구가 필요한 요구를 해시로 해결하려 하지 않는다
|
||||
|
||||
## 6. 위치별 규칙
|
||||
|
||||
### 6.1 request logging
|
||||
|
||||
OWASP는 HTTP request body, response body, headers 같은 확장 상세 정보는 민감할 수 있으므로 특별한 주의가 필요하다고 설명합니다. 이 프로젝트에서는 request logging에서 다음을 기본 금지한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- request body 전문 로그 금지
|
||||
- Authorization 헤더 원문 금지
|
||||
- Cookie 헤더 원문 금지
|
||||
- query string 전체 로그 금지
|
||||
- multipart 파일명/본문 원문 금지
|
||||
|
||||
허용 가능한 예:
|
||||
|
||||
- requestPath
|
||||
- method
|
||||
- contentType
|
||||
- payloadBytes
|
||||
- allowlist된 소수의 비민감 query field
|
||||
|
||||
### 6.2 response logging
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- response body 전문 로그 금지
|
||||
- token 발급 응답, 인증 응답, 결제 응답 원문 금지
|
||||
- 상태코드, duration, 외부 시스템 상태, 응답 크기 같은 메타데이터만 우선 기록한다
|
||||
|
||||
### 6.3 exception logging
|
||||
|
||||
OWASP는 stack trace, system error messages, debug information, request/response body 등이 민감해질 수 있다고 설명합니다. 따라서 예외 로그는 stack trace를 남기더라도 민감 데이터를 포함한 message/context를 같이 남기지 않도록 조심해야 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 예외 메시지에 포함된 민감정보 원문을 그대로 로그 제목에 사용하지 않는다
|
||||
- log.error(ex.getMessage(), ex)를 기본값으로 두지 않는다
|
||||
- 사건 설명 + 안전한 식별자 + 예외 객체 순으로 기록한다
|
||||
- stack trace에 비밀값이 포함될 위험이 있는 경로는 별도 검토한다
|
||||
|
||||
### 6.4 external API / integration logging
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 외부 요청/응답 payload 전문 로그 금지
|
||||
- 외부 API key, bearer token, cookie 원문 금지
|
||||
- provider 이름, endpoint path, status, duration, request id 같은 메타데이터만 우선 기록
|
||||
- third-party error body도 민감정보를 포함할 수 있으므로 원문 그대로 남기지 않는다
|
||||
|
||||
### 6.5 audit/security logging
|
||||
|
||||
OWASP는 보안/감사 로그도 중요하지만, 그렇다고 민감값을 그대로 기록하라는 뜻은 아니라고 설명합니다. “무엇이 일어났는지”는 남기되, 값 원문은 보호해야 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- “비밀번호 변경 시도”, “토큰 재발급 요청”, “민감 데이터 조회” 같은 사건 자체는 기록
|
||||
- 하지만 실제 비밀번호, 토큰, 데이터 원문은 기록하지 않는다
|
||||
- audit log가 필요하면 일반 운영 로그와 목적을 구분한다
|
||||
|
||||
## 7. structured logging / MDC 규칙
|
||||
|
||||
### 7.1 MDC에 넣는 값도 같은 기준으로 마스킹한다
|
||||
|
||||
Spring Boot는 MDC 값을 로그 패턴과 structured JSON에 포함할 수 있고, ECS/GELF/Logstash 포맷에서도 MDC key-value가 JSON에 들어갑니다. 따라서 MDC는 안전한 공통 필드만 넣는 용도로 써야 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- MDC에는 traceId, requestPath, actorId, operation, clientIp(필요 시) 정도의 안전한 메타데이터만 둔다
|
||||
- MDC에 token, email 전체값, session id, raw principal object를 넣지 않는다
|
||||
- “MDC니까 괜찮다”는 예외를 두지 않는다
|
||||
|
||||
### 7.2 structured logging JSON 필드도 마스킹 정책 대상이다
|
||||
|
||||
Spring Boot는 structured JSON에서 include/exclude/rename/add, customizer를 지원합니다. 이는 JSON 로그가 plain text보다 안전하다는 뜻이 아니라, 같은 보호 규칙을 중앙에서 적용할 수 있다는 뜻에 가깝다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- structured logging을 쓰면 JSON field 수준 마스킹/제외 규칙을 검토한다
|
||||
- 수집 시스템에 맞춘 필드 rename은 허용하지만, 민감 필드 유입 자체를 방지하는 것이 우선이다
|
||||
- ingestion 단계에서 추가 마스킹이 가능해도 애플리케이션 단계의 원문 출력 금지를 대체하지 않는다
|
||||
|
||||
## 8. 로그 인젝션 방지 규칙
|
||||
|
||||
### 8.1 외부 입력은 로깅 전에 sanitization을 고려한다
|
||||
|
||||
OWASP는 다른 trust zone에서 들어오는 이벤트 데이터는 형식 검증을 하고, CR/LF 및 delimiter 같은 문자를 정제해 로그 인젝션을 막으라고 권고합니다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 사용자 입력, 외부 시스템 응답, 헤더, query parameter를 로그에 넣기 전 길이/형식 검토
|
||||
- CR/LF, 탭, 구분자, 제어문자 정제
|
||||
- multi-line injection이 가능한 원문을 그대로 로그 메시지에 넣지 않는다
|
||||
|
||||
### 8.2 예외 메시지와 외부 오류 메시지도 신뢰하지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 외부 시스템의 에러 메시지를 그대로 로그 제목에 사용하지 않는다
|
||||
- 예외 메시지를 한 줄 요약/정제 후 보조 정보로만 사용한다
|
||||
- 로그 제목은 애플리케이션이 통제하는 문장으로 쓴다
|
||||
|
||||
## 9. 구현 규칙
|
||||
|
||||
### 9.1 공통 마스킹 유틸/컴포넌트를 둔다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 이메일, 전화번호, 토큰, 계좌/카드 마스킹은 공통 유틸이나 formatter로 제공한다
|
||||
- 서비스마다 제각각 다른 마스킹 패턴을 쓰지 않는다
|
||||
- 로그 메시지 안에서 직접 substring으로 잘라 쓰는 임시 구현을 줄인다
|
||||
|
||||
### 9.2 logger 호출 전에 안전한 값으로 변환한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- log.info("...", rawToken) 금지
|
||||
- log.info("...", maskedToken(rawToken)) 형태로만 허용
|
||||
- 가능하면 값을 마스킹한 뒤 변수에 담아 의미 있는 이름으로 사용한다
|
||||
|
||||
### 9.3 가능한 경우 중앙 로깅 구성에서 2차 필터링을 둔다
|
||||
|
||||
Spring Boot는 structured logging JSON customizer와 MDC 기반 필드 구성을 지원합니다. 이를 이용해 민감 필드가 특정 이름으로 유입될 경우 제거/대체하는 2차 방어선을 둘 수 있다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- authorization, accessToken, password, sessionId 같은 공통 금지 키는 중앙 필터링을 검토한다
|
||||
- 다만 중앙 필터링이 있으니 코드에서 원문을 찍어도 된다는 뜻은 아니다
|
||||
|
||||
## 10. 테스트/검증 규칙
|
||||
|
||||
### 10.1 로그에 금지 데이터가 실제로 남지 않는지 검증한다
|
||||
|
||||
OWASP는 로깅 메커니즘의 설계/구현/검증을 강조합니다. 이 프로젝트도 마스킹 정책을 문서만 두지 않고 테스트/리뷰 대상으로 본다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 주요 인증/결제/개인정보 흐름은 로그 검증 테스트를 둘 수 있다
|
||||
- 보안 리뷰 체크리스트에 “민감정보 로그 노출 여부”를 포함한다
|
||||
- 샘플 로그/운영 로그 점검으로 정책 위반을 찾아낸다
|
||||
|
||||
### 10.2 규칙 위반은 기능 버그가 아니라 보안 버그로 취급한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 토큰/비밀번호/세션 ID/PII 원문 노출은 보안 결함으로 분류
|
||||
- “로그일 뿐”이라고 축소하지 않는다
|
||||
- 수정 우선순위를 높게 둔다
|
||||
|
||||
## 11. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- 비밀번호 원문 로그
|
||||
- access token / refresh token / session id / cookie 값 원문 로그
|
||||
- Authorization 헤더 원문 로그
|
||||
- request/response body 전문 로그
|
||||
- 이메일/전화번호/이름 등 개인식별자 전체값 무비판적 로그
|
||||
- 외부 API secret, DB connection string, key material 로그
|
||||
- DEBUG/TRACE라는 이유로 민감정보 예외 허용
|
||||
- MDC/structured JSON에 민감정보 적재
|
||||
- CR/LF 등 제어문자 포함 원문을 그대로 로그에 기록
|
||||
|
||||
## 12. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- 이 값은 정말 로그에 남겨야 하는가?
|
||||
- 원문 대신 내부 식별자/마스킹/해시로 충분하지 않은가?
|
||||
- 토큰, 세션 ID, 비밀번호, key material, 결제정보가 원문으로 남지 않는가?
|
||||
- request/response 전문을 기본 로그에 넣고 있지 않은가?
|
||||
- MDC와 structured logging 필드에도 같은 보호 규칙이 적용되는가?
|
||||
- 외부 입력을 로그에 넣기 전에 sanitization을 검토했는가?
|
||||
- 공통 마스킹 유틸/구성과 코드 레벨 금지 규칙이 함께 적용되는가?
|
||||
@@ -0,0 +1,302 @@
|
||||
# Trace / Principal / Path Recording 기준
|
||||
|
||||
## 1. 목적
|
||||
|
||||
이 문서는 운영 로그와 트레이싱에서 traceId, principal(현재 사용자/호출 주체), requestPath를 어디서, 어떤 이름으로, 어느 정도까지 기록할지 정의한다.
|
||||
|
||||
이 문서의 목표는 다음과 같다.
|
||||
|
||||
- 한 요청/작업을 trace 단위로 추적 가능하게 만든다
|
||||
- 사용자/호출 주체와 요청 경로를 일관된 키로 검색 가능하게 만든다
|
||||
- Security 타입과 Servlet 저수준 접근이 여러 계층으로 퍼지는 것을 막는다
|
||||
- 민감정보를 보호하면서도 운영에 필요한 상관관계를 유지한다
|
||||
|
||||
## 2. 근거 수준
|
||||
|
||||
- Official: Spring Boot / Spring Framework / Spring Security 공식 문서에서 직접 확인되는 내용
|
||||
- Official + Practice: 공식 기능 위에 일반적인 운영 관행을 결합한 내용
|
||||
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
|
||||
|
||||
## 3. 기본 원칙
|
||||
|
||||
### 3.1 traceId, principal, requestPath는 운영 상관관계의 핵심 키다
|
||||
|
||||
Spring Boot는 tracing이 켜져 있으면 로그에 correlation ID를 기본 포함할 수 있다고 설명한다. 이 프로젝트에서는 그 위에 누가(actor/principal), 어떤 경로(requestPath) 에서 발생한 사건인지까지 함께 남겨서, 장애 분석과 감사 추적이 가능한 최소 세트를 만든다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 대표 운영 로그는 가능한 한 traceId, principal/actor, requestPath 중 필요한 값을 함께 가진다
|
||||
- 세 값은 “있으면 좋은 정보”가 아니라, 요청/작업 상관 분석의 기본 키로 본다
|
||||
- 다만 모든 로그에 세 값을 기계적으로 다 넣는 것은 지양하고, 사건 종류에 맞게 최소 세트를 고른다
|
||||
|
||||
### 3.2 공통 상관 키는 로그 패턴/MDC에, 비즈니스 식별자는 메시지 필드에 둔다
|
||||
|
||||
Spring Boot는 correlation ID를 로그 패턴에 포함시키고, 형식도 조정할 수 있게 한다. 따라서 trace/correlation 같은 공통 값은 패턴/MDC 레벨에서 처리하고, principal, requestPath, operation, resourceId 같은 값은 로그 메시지 필드로 남기는 것이 자연스럽다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- traceId는 기본적으로 로그 패턴/MDC에 두는 것을 우선
|
||||
- principal, requestPath, operation, resourceId는 메시지 key-value 필드로 남긴다
|
||||
- 이미 패턴에 있는 값을 메시지에 불필요하게 중복하지 않는다
|
||||
|
||||
### 3.3 principal 기록은 인증 객체 접근 방식과 분리해서 생각하지 않는다
|
||||
|
||||
Spring Security는 @AuthenticationPrincipal과 메타 애노테이션 기반 @CurrentUser 패턴을 제공한다. 이 프로젝트에서는 현재 사용자 접근 방식과 principal 로깅 규칙을 같은 방향으로 맞춘다. 즉, controller가 SecurityContextHolder를 직접 뒤져서 principal을 로그에 넣는 식의 접근을 기본 금지한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- principal 기록은 @CurrentUser 같은 전용 현재 사용자 접근 규칙과 함께 설계한다
|
||||
- 보안 프레임워크 내부 타입 전체를 로그에 덤프하지 않는다
|
||||
- 로그에는 필요한 최소 principal 식별자만 남긴다
|
||||
|
||||
## 4. TraceId 기록 규칙
|
||||
|
||||
### 4.1 traceId는 tracing이 활성화된 서비스에서 기본적으로 로그에 포함되어야 한다
|
||||
|
||||
Spring Boot는 Micrometer Tracing을 사용하는 경우 로그에 correlation ID를 기본 포함할 수 있다고 설명한다. 기본 correlation ID는 traceId-spanId 형태다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- tracing이 켜진 서비스는 운영 로그에 trace/correlation 식별자가 기본 포함되어야 한다
|
||||
- traceId는 가능한 한 로그 패턴/MDC 레벨에서 일관되게 출력한다
|
||||
- 서비스마다 trace 키 이름이나 형식을 제각각 바꾸지 않는다
|
||||
|
||||
### 4.2 traceId는 대표 로그 검색의 1차 키로 본다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청 실패, 외부 연동 실패, 배치 실패 같은 대표 로그는 trace로 묶여야 한다
|
||||
- 한 요청 흐름에서 여러 로그가 흩어져도 traceId로 묶어 검색 가능해야 한다
|
||||
- traceId가 없다면 같은 요청의 controller/client/db 연관 로그를 잇기 어렵다는 점을 기본 전제로 둔다
|
||||
|
||||
### 4.3 비동기/스케줄/외부 연동 로그도 가능한 한 trace 연결성을 유지한다
|
||||
|
||||
Spring Boot observability는 logging, metrics, traces를 함께 다루고, tracing은 서비스 내부/외부 경계를 따라 상관관계를 유지하는 데 쓰인다. 이 프로젝트에서는 비동기 작업이나 외부 연동도 가능한 한 원 요청과 연결 가능한 trace 문맥을 유지하는 방향을 기본 권장으로 둔다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 동기 요청 경로에서 시작한 비동기 후속 작업은 가능한 경우 trace 연결성을 유지한다
|
||||
- 스케줄/배치처럼 원 요청이 없는 작업은 자체 trace/correlation을 생성해 대표 로그를 남긴다
|
||||
- 외부 시스템 호출 로그도 내부 요청 trace와 연계되는 쪽을 우선한다
|
||||
|
||||
## 5. Principal 기록 규칙
|
||||
|
||||
### 5.1 principal은 “누가 호출했는가”를 식별하는 최소 값만 남긴다
|
||||
|
||||
Spring MVC는 Principal을 메서드 인자로 지원하고, Spring Security는 @AuthenticationPrincipal로 principal을 직접 주입할 수 있다. 하지만 이 프로젝트에서 로그에 남길 principal은 Authentication 전체나 raw claim map이 아니라, 운영에 필요한 최소 식별자다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 기본 로그 principal 필드명은 actorId 또는 principalId 중 하나로 통일한다
|
||||
- 권장 기본값은 actorId
|
||||
- principal 전체 객체, authorities 전체, credentials, token 원문은 로그에 남기지 않는다
|
||||
|
||||
### 5.2 인증된 사용자가 없으면 그 상태도 일관되게 표현한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 비로그인 요청, 공개 endpoint, 시스템 내부 작업은 principal 부재를 일관되게 표현한다
|
||||
- 예:
|
||||
- actorId=anonymous
|
||||
- actorId=system
|
||||
- actorId=batch
|
||||
- null, empty string, guest, unknownUser 같은 값을 서비스마다 섞지 않는다
|
||||
|
||||
### 5.3 principal은 사람이 직접 식별되는 값보다 내부 식별자를 우선한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 이메일, 전화번호, 로그인 이름 전체값보다 내부 userId/subjectId를 우선 기록한다
|
||||
- 외부 노출 식별자가 꼭 필요해도 전체 원문 대신 최소한으로 남긴다
|
||||
- principal 로깅이 곧 PII 노출로 이어지지 않게 한다
|
||||
|
||||
### 5.4 principal 로깅은 controller/web 경계에서 필요한 값을 추출해 전달한다
|
||||
|
||||
Spring Security는 @AuthenticationPrincipal과 메타 애노테이션 기반 접근을 제공하므로, controller/web 경계에서 전용 현재 사용자 타입 또는 필요한 필드만 받을 수 있다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- controller는 @CurrentUser 또는 @CurrentUserId 같은 방식으로 actor 정보를 얻는다
|
||||
- service/domain/util에서 SecurityContextHolder를 다시 조회하지 않는다
|
||||
- 로그에 principal이 필요하면 web 경계나 공통 interceptor/filter가 이를 정규화해서 넣는다
|
||||
|
||||
## 6. RequestPath 기록 규칙
|
||||
|
||||
### 6.1 requestPath는 대표 HTTP 로그의 기본 필드다
|
||||
|
||||
Spring Framework는 request logging filter 계열에서 request URI와 필요 시 query string도 로그에 넣을 수 있게 제공한다. 또한 OncePerRequestFilter는 요청 시작 시 1회 실행을 기본으로 한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 대표 HTTP 로그에는 requestPath를 기본 필드로 둔다
|
||||
- 필드명은 requestPath로 통일한다
|
||||
- path, uri, requestUri, url을 혼용하지 않는다
|
||||
|
||||
### 6.2 기본값은 path만 기록하고, query string은 선택적으로 다룬다
|
||||
|
||||
AbstractRequestLoggingFilter 계열은 query string을 선택적으로 포함할 수 있다. 이는 query string이 항상 로그에 적합한 것은 아니라는 뜻이기도 하다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 기본 기록 대상은 path만
|
||||
- query string은 기본적으로 로그에 포함하지 않는다
|
||||
- query string이 운영상 꼭 필요하면 별도 마스킹/allowlist 기준 아래 제한적으로 기록한다
|
||||
|
||||
### 6.3 경로는 템플릿이 아니라 실제 요청 path를 기본으로 한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 기본 requestPath는 실제 요청된 path
|
||||
- 예: /api/v1/users/123
|
||||
- 다만 집계/카디널리티 문제가 크면 별도 필드로 route template를 함께 관리할 수 있다
|
||||
- 예:
|
||||
- requestPath=/api/v1/users/123
|
||||
- route=/api/v1/users/{userId}
|
||||
|
||||
이 항목은 실무 운영 편의를 위한 Practice + Project Recommendation 이다.
|
||||
|
||||
## 7. 기록 위치 규칙
|
||||
|
||||
### 7.1 공통 HTTP 기록은 filter 또는 interceptor에서 수행할 수 있다
|
||||
|
||||
Spring Framework는 OncePerRequestFilter를 통해 요청 시작 시 1회 실행되는 filter를 만들 수 있고, request logging filter 계열도 제공한다. 또한 Spring MVC interceptor는 handler 전후 공통 처리에 쓰인다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- traceId, requestPath, 시작 시각, 응답 상태 같은 공통 HTTP 기록은 filter 또는 interceptor에서 공통 처리할 수 있다
|
||||
- 인증 완료 이후 principal까지 같이 기록해야 하면 보안 필터 체인 이후 시점을 고려한다
|
||||
- request/response wrapping 같은 HTTP concern은 filter, handler 전후 메타데이터는 interceptor 쪽을 우선 검토한다
|
||||
|
||||
### 7.2 대표 요청 로그는 한 곳에서 남긴다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청 시작/완료/실패 대표 로그는 공통 컴포넌트 한 곳에서 남긴다
|
||||
- controller마다 요청 진입/종료 로그를 반복 작성하지 않는다
|
||||
- 공통 요청 로그와 개별 비즈니스 로그를 구분한다
|
||||
|
||||
### 7.3 principal이 결정되기 전/후를 구분한다
|
||||
|
||||
Spring Security 문서는 커스텀 필터가 현재 사용자를 알아야 한다면 인증 필터 뒤에 배치해야 함을 설명한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청 시작 시점에는 principal이 아직 없을 수 있다
|
||||
- principal까지 포함한 대표 로그가 필요하면 인증 이후 시점에서 기록한다
|
||||
- “pre-auth request log”와 “authenticated request log”를 같은 규칙 없이 섞지 않는다
|
||||
|
||||
## 8. MDC / Structured Logging 규칙
|
||||
|
||||
### 8.1 traceId는 MDC/패턴에, actorId/requestPath는 필요 시 MDC 또는 구조화 필드에 둔다
|
||||
|
||||
Spring Boot는 correlation ID를 로그 패턴에 포함하고, 구조화 로그도 지원한다.
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- traceId는 패턴/MDC 기본값으로 두는 것을 우선
|
||||
- actorId, requestPath는
|
||||
- 메시지 key-value
|
||||
- MDC
|
||||
- structured JSON field
|
||||
- 중 하나로 일관되게 선택한다
|
||||
- 같은 서비스 안에서 세 방식을 뒤섞지 않는다
|
||||
|
||||
### 8.2 MDC를 쓴다면 누수 없이 정리한다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 요청 단위 MDC 값은 요청 종료 시 반드시 정리한다
|
||||
- async 경계가 있으면 MDC 전파/정리 전략을 별도 검토한다
|
||||
- 이전 요청의 principal/path가 다음 로그에 새어 나가지 않게 한다
|
||||
|
||||
## 9. 민감정보/카디널리티 규칙
|
||||
|
||||
### 9.1 principal과 path는 유용하지만 무제한으로 남기지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- principal은 내부 식별자 중심
|
||||
- path는 기본 path만
|
||||
- query string, 전체 URL, raw header, token, cookie는 기본 금지
|
||||
- traceId는 민감정보가 아니더라도 외부 공개 출력 정책은 별도 검토한다
|
||||
|
||||
### 9.2 고카디널리티 값을 메트릭 태그처럼 남발하지 않는다
|
||||
|
||||
프로젝트 규칙:
|
||||
|
||||
- 로그 본문에는 requestPath=/api/v1/users/123 같은 고유 path가 들어갈 수 있다
|
||||
- 하지만 메트릭 태그/라벨에는 그대로 쓰지 않고 route template를 우선 검토한다
|
||||
- observability에서 로그와 메트릭의 카디널리티 전략을 혼동하지 않는다
|
||||
|
||||
## 10. 권장 기본 필드 세트
|
||||
|
||||
### 10.1 대표 요청 완료 로그
|
||||
|
||||
권장 필드:
|
||||
|
||||
- requestPath
|
||||
- method
|
||||
- status
|
||||
- durationMs
|
||||
- actorId(가능할 때)
|
||||
- trace/correlation ID(패턴/MDC)
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
Completed request. requestPath=/api/v1/users/123 method=GET status=200 durationMs=34 actorId=u_001
|
||||
```
|
||||
|
||||
### 10.2 대표 요청 실패 로그
|
||||
|
||||
권장 필드:
|
||||
|
||||
- requestPath
|
||||
- method
|
||||
- status
|
||||
- errorCode
|
||||
- durationMs
|
||||
- actorId(가능할 때)
|
||||
- trace/correlation ID
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
Failed request. requestPath=/api/v1/users method=POST status=500 errorCode=INTERNAL_SERVER_ERROR durationMs=88 actorId=u_001
|
||||
```
|
||||
|
||||
### 10.3 외부 연동 실패 로그
|
||||
|
||||
권장 필드:
|
||||
|
||||
- requestPath
|
||||
- actorId
|
||||
- externalSystem
|
||||
- status
|
||||
- errorCode
|
||||
- durationMs
|
||||
- trace/correlation ID
|
||||
|
||||
## 11. 금지 규칙
|
||||
|
||||
다음은 기본 금지다.
|
||||
|
||||
- controller/service/util에서 SecurityContextHolder 직접 조회 후 제각각 principal 로깅
|
||||
- requestPath, path, uri, url 등 키 이름 혼용
|
||||
- query string 전체를 기본 로그에 포함
|
||||
- principal 전체 객체 또는 JWT/raw token 덤프
|
||||
- 요청 대표 로그를 여러 레이어에서 중복 출력
|
||||
- MDC 값 정리 없이 다음 요청으로 누수
|
||||
- traceId 없이 대표 오류 로그를 남겨 상관관계가 끊기는 것
|
||||
|
||||
## 12. 체크리스트
|
||||
|
||||
다음 질문에 “예”로 답할 수 있어야 한다.
|
||||
|
||||
- trace/correlation 식별자가 대표 로그에 연결되는가?
|
||||
- principal은 최소 식별자만 기록되는가?
|
||||
- requestPath 필드명이 일관적인가?
|
||||
- query string/민감정보를 기본으로 남기지 않는가?
|
||||
- principal 기록 시점이 인증 완료 여부와 맞는가?
|
||||
- 공통 요청 로그가 한 곳에서 일관되게 생성되는가?
|
||||
- MDC/structured logging 전략이 누수 없이 운영 가능한가?
|
||||
Reference in New Issue
Block a user