로깅 규칙
기본 원칙
System.out.println절대 금지 →@Slf4j사용- 문자열
+연산 금지 →{}바인딩 사용 (지연 평가로 성능 향상) - 로그에 개인정보 (이름, 이메일, 전화번호), 비밀번호, 토큰 출력 금지
- 운영 환경에서
DEBUG비활성화 ERROR레벨 발생 시 Slack 알람 자동 발송 (GlobalExceptionHandler연동)
로거 선언
// Lombok @Slf4j 어노테이션 사용 (권장)
@Slf4j
@Service
public class NewsService {
// log.info(), log.warn(), log.error() 바로 사용
}
// 직접 선언 (Lombok 미사용 시)
private static final Logger log = LoggerFactory.getLogger(NewsService.class);
로그 레벨 기준
| 레벨 | 사용 상황 | 예시 |
|---|---|---|
ERROR | 장애, 복구 불가능한 오류 | DB 연결 실패, 외부 API 오류 |
WARN | 주의, 비정상이지만 계속 가능 | 재시도, 예상치 못한 상태 |
INFO | 주요 비즈니스 흐름 | 배치 시작/완료, 수집 결과 |
DEBUG | 개발용 상세 정보 | 파라미터 값, 중간 계산 |
올바른 로깅 예시
// INFO — 주요 비즈니스 이벤트
log.info("다이제스트 배치 시작");
log.info("뉴스 수집 완료: source={}, count={}", source, count);
log.info("다이제스트 메일 발송 완료: to={}", mailTo);
// WARN — 비정상이지만 계속 가능한 상황
log.warn("뉴스 수집 일부 실패: source={}, error={}", source, e.getMessage());
log.warn("외부 API 응답 지연: url={}, elapsed={}ms", url, elapsed);
log.warn("Slack 알림 전송 실패: {}", e.getMessage());
// ERROR — 스택트레이스 포함 (Slack 알람 트리거)
log.error("DB 연결 실패: datasource={}", datasource, exception);
log.error("뉴스 수집 실패: source={}", source, e);
// DEBUG — 개발 환경에서만 출력
log.debug("쿼리 파라미터: keyword={}, page={}", keyword, page);
잘못된 로깅 (금지)
// 개인정보 출력 금지
log.info("사용자: email={}, phone={}", email, phone); // 금지
log.info("로그인: password={}", password); // 절대 금지
log.info("API 키: key={}", apiKey); // 절대 금지
// 문자열 연결 금지 (성능 저하)
log.info("처리 완료: " + count + "건"); // 금지
// System.out.println 금지
System.out.println("배치 시작"); // 금지
// 스택트레이스를 응답에 노출 금지 (로그에만)
return ResponseEntity.status(500).body(e.getMessage()); // 스택트레이스 노출 가능성
Slack 알람 연동
GlobalExceptionHandler에서 500 에러 발생 시 자동으로 Slack 알람이 발송된다.
// GlobalExceptionHandler 내부
slackService.sendError(request.getRequestURI(), e);
배치 실패 시 직접 호출:
@Slf4j
@Component
@RequiredArgsConstructor
public class DigestScheduler {
private final SlackService slackService;
@Scheduled(cron = "0 0 7 * * *")
public void runDigest() {
try {
digestOrchestrator.runDigest();
log.info("다이제스트 배치 완료");
} catch (Exception e) {
log.error("다이제스트 배치 실패", e);
slackService.sendBatchFail("DigestScheduler", e);
}
}
}
Logback 설정 (logback-spring.xml)
<!-- src/main/resources/logback-spring.xml -->
<configuration>
<springProfile name="!prod">
<!-- 개발 환경: 콘솔 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.scraping.agent" level="DEBUG"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<!-- 운영 환경: 파일 출력 (rolling) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/agent/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/agent/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
<logger name="com.scraping.agent" level="INFO"/>
<!-- 운영 환경: SQL 쿼리 로그 비활성화 -->
<logger name="org.hibernate.SQL" level="WARN"/>
</springProfile>
</configuration>
application.yml 로깅 설정
logging:
level:
root: INFO
com.scraping.agent: INFO
org.hibernate.SQL: ${SQL_LOG_LEVEL:WARN} # 개발: DEBUG, 운영: WARN
org.springframework.security: WARN
보안 체크리스트
- [ ] 로그에 개인정보(이름, 이메일, 전화번호) 미포함
- [ ] 로그에 비밀번호, 토큰, API 키 미포함
- [ ] 운영 환경 DEBUG 로그 비활성화
- [ ] 예외 로그: 스택트레이스는 로그에만, 응답에는 일반 메시지
- [ ]
System.out.println사용 없음 - [ ] 문자열
+연결 없음 →{}바인딩 사용