로깅(Logging)
로그를 남기는 것
- 로깅이 필요한 이유
- 로깅 위치, 메시지 수준 선정 전략
- 디버깅용, 검사용 로그의 차이점
- 파일 기반 로깅 및 외부 시스템 연동 구조에 대해
로깅
애플리케이션이 실행되는 동안 발생하는 다양한 정보를 기록해서 저장하는 행위
예외가 발생했을 때 이유를 확인하고 보안 검사를 위해 기록할때도 쓰임
| 목적 | |
| 애플리케이션 동작 상황 파악 | 시스템이 어떻게 처리되고 있는가? |
| 문제 상황 분석 | 예외가 발생했을 때 어떻게 발생했고, 가장 중요한 원인이 무엇인가? |
| 비즈니스 이벤트 추적 | 사용자 로그인, 주문 생성 등 중요 통계 기록 가능 |
| 보안 검사 | 누가 어느때, 어느 데이터에 접근했는가? |
로깅 예시
1. 애플리케이션 동작 상황 파악하기(서비스임)
@Service // 서비스임
public class UserService {
// Logger타입의 필드 생성
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User getUserById(Long id) {
logger.debug("getUserById 메서드 호출 : id : {}", id);
}
// 메서드 실행 시간을 체크함
long startTime = System.currentTimeMillis(); // 먼저 startTime을 생성하고
User user = userRepository.findById(id).orElse(null); // user객체를 생성한 다음에
long endTime = System.currentTimeMillis(); // endTime을 생성한다
if(user != null) {
logger.debug("사용자 조회 완 : {}, 소용시간 : {}ms", user.getUsername(),(endTime - startTime));
} else {
logger.warn("ID가 {}인 사용자를 찾을 수 없음", id);
}
return user;
}
debug()의 {} 안에 들어갈 인자는 콤마(,)를 통해 구분해서 지정할 수 있나보다. 마치 printf() 같은 느낌
2. 문제 상황 분석과 해결하기(컨트롤러임)
@Controller // 컨트롤러임
public class ProductController {
// Logger 필드 생성
private static final Logger logger = LoggerFactory.getLogger(ProductController.class);
@GetMapping("/products/{id}")
public String getProduct(@PathVariable Long id, Model model) {
try {
Product product = productService.getProductById(id); // 서비스 불러옴
model.addAttribute("product", product); // 모델
logger.info("제품 페이지 조회 성공! id = {}, name = {}", id, product.getName());
return "product/detail";
} catch(ProductNotFoundException e) { // 첫번째 catch문
logger.warn("존재하지 않는 제품이에요! id = {}",id, e); // id가 {}안에 들어간다
return "product/not-found";
} catch(Exception e) { // 두번째 catch문
logger.error("제품 조회 중 예상치 못한 오류 발생! id = {}", id, e);
return "error/500";
}
}
}
try - catch문에서 catch문이 2개 들어가도 된다는걸 방금 알게 되었다.
오...
3. 비즈니스 이벤트 추적 및 기록(로그), 서비스임
@Service // 서비스임
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class); // 필드
@Transactional
public Order placeOrder(OrderRequest orderRequest, User user) { // orderRequest, user를 받음
logger.info("주문시작: 사용자 = {}, 상품 수 = {}, 총액 = {}",
user.getUsername(),
orderRequest.getItems().size(),
orderRequest.getTotalAmount()
); // 로직을 실행한다. 빵 틀임 재료 안들어감
Order order = new Order();
// 핵심 로직이 들어가는 곳, 주문 처리
logger.info("주문 완료 : 주문번호 = {}, 사용자 = {}, 결제방법 = {}",
order.getOrderNumber(),
user.getUsername(),
order.getPaymentMethod()
);
return order;
}
}
로그를 남기는 형식은 정해져있는 것 같다. 숙지하면 잘 쓸 수 있을 것 같은 기분
4. 보안 검사(audit), 서비스임
@Service // 서비스임
public class UserSecurityService { // User에 대한 보안 로직은 담당하는 서비스같음
// 생각해보니 Logger 필드는 항상 private static final이 붙는구나
private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY_AUDIT");
public void login(String username, String inAddress) {
sequrityLogger.info("사용자 로그인 : username = {}, ip = {}, time = {}",
username, ipAddress, LocalDateTime.now());
}
public void accessSensitiveData(Data data, String dataType) {
securityLogger.info("민감한 데이터 접근 : username = {}, dataType = {}, time = {}",
user.getUsername(), dataType, LocalDateTime.now());
}
public void changeUserRole(User admin, User targetUser, String newRole) {
securityLogger.info("사용자 권한 변경 : admin = {}, targetUser = {}, newRole = {}, time = {}",
admin.getUsername(), targetUser.getUsername(), newRole, LocalDateTime.now());
}
}
서비스로 따로 관리한다
* 로그는 검사용, 디버그용, 보안검사용으로 목적이 다르면 다르게 구성해야한다
* 메세지 레벨(INFO / WARN / ERROR)을 명확하게 구분해서 사용해야함
* 로그에 민감한 정보(비밀번호, 카드번호 등)는 절대 기록되지 않게 해야함
'Spring Boot' 카테고리의 다른 글
| 로깅 : 환경별 로깅 전략 (6) | 2025.08.13 |
|---|---|
| 로깅 : SLF4J, Logback (5) | 2025.08.12 |
| 예외 처리 : 예외 계층 구조 설계 (5) | 2025.08.12 |
| 예외 처리 : Spring 예외 처리 아키텍처에 대해서 (9) | 2025.08.12 |
| 예외 처리 : 예외 처리의 필요성 (1) | 2025.08.12 |