어제 오늘 내일

[Spring Boot] 기초편 - System.out.println은 그만! 로그(Log) 제대로 찍기 본문

IT/SpringBoot

[Spring Boot] 기초편 - System.out.println은 그만! 로그(Log) 제대로 찍기

hi.anna 2026. 2. 26. 07:52

 

스프링 부트에서는 System.out.println() 대신 SLF4J라는 인터페이스와 Logback이라는 구현체를 기본으로 사용합니다. 이를 가장 쉽게 쓰는 방법은 롬복(Lombok)의 @Slf4j 어노테이션을 사용하는 것입니다.

기초부터 차근차근 알려드릴게요.
 

1. 의존성 설정

dependencies {
    // 1. Spring Boot Web (여기에 로깅 라이브러리(Logback)가 이미 들어있습니다!)
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // 2. Lombok (편하게 로그를 찍기 위해 필수)
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    // 테스트 코드에서도 롬복을 쓰고 싶다면 추가
    testImplementation 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
}

2. 기본 사용법 (@Slf4j)

클래스 위에 @Slf4j만 붙이면 마법처럼 log라는 변수가 생깁니다.

import lombok.extern.slf4j.Slf4j; // 롬복 임포트
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // ★ 이거 하나면 준비 끝!
@RestController
public class TestController {

    @GetMapping("/log-test")
    public String logTest() {
        String name = "홍길동";

        // 1. 문자열 연결 (+) 절대 금지! (메모리 낭비)
        // System.out.println("이름은 " + name + "입니다."); 

        // 2. 중괄호 {} 를 사용하세요 (치환 문자)
        log.info("사용자가 접속했습니다. 이름: {}", name);

        return "ok";
    }
}

결과 콘솔:
2025-12-26 14:30:00.123 INFO 12345 --- [nio-8080-exec-1] c.e.board.controller.TestController : 사용자가 접속했습니다. 이름: 홍길동


3. 실무 코드에 적용해보기

우리가 만들었던 MemberControllerMemberService에 로그를 심어보겠습니다.

① MemberController (요청 받음)

사용자가 로그인 데이터를 보냈을 때 로그를 남깁니다.

@Slf4j // ★ 추가
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/login")
    public JwtToken login(@RequestBody MemberLoginDto memberLoginDto) {
        // [주의] 비밀번호는 절대 로그에 찍으면 안 됩니다! 보안 사고입니다.
        log.info("로그인 요청 들어옴 - username: {}", memberLoginDto.getUsername());

        JwtToken token = memberService.login(memberLoginDto.getUsername(), memberLoginDto.getPassword());

        log.info("로그인 성공, 토큰 발급 완료");
        return token;
    }
}

② MemberService (로직 수행)

비즈니스 로직 중간중간 중요한 지점에 로그를 남깁니다.

@Slf4j // ★ 추가
@Service
// ...
public class MemberService {

    // ...

    @Transactional
    public JwtToken login(String username, String password) {
        log.info("Service 로그인 로직 시작 - ID: {}", username);

        // 1. 검증
        // ... (생략)

        // 2. 인증 객체 생성
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        log.info("DB 인증 통과 (비밀번호 일치)");

        // 3. 토큰 생성
        JwtToken jwtToken = jwtTokenProvider.createToken(authentication);

        // ... (생략)

        return jwtToken;
    }
}

4. 로그 레벨 (Level) 이해하기

로그에는 계급(Level)이 있습니다. 중요도에 따라 골라 써야 합니다.
(낮음 🔽 ~ 높음 🔼)

  1. log.trace("..."): 정말 미세한 동작 하나하나 추적할 때 (나사 하나까지 확인)
  2. log.debug("..."): 개발할 때 변수 값 확인용, DB 쿼리 확인용 (개발자용)
  3. log.info("..."): [기본] 운영 시 확인해야 할 정보 (로그인 성공, 주문 완료 등)
  4. log.warn("..."): 에러는 아닌데 주의해야 함 (비번 5회 틀림, 디스크 용량 80% 참)
  5. log.error("..."): [비상] 에러 발생! (DB 연결 끊김, 널포인트 익셉션)

[설정 파일에서 조절하기]
application.yml에서 "어느 레벨부터 출력할지" 정할 수 있습니다.

logging:
  level:
    root: INFO                 # 기본적으로 INFO 이상만 출력 (trace, debug 숨김)
    com.example.board: DEBUG   # 하지만 내 프로젝트 패키지는 DEBUG 부터 다 보여줘!

5. 왜 System.out.println을 쓰면 안 되나요?

면접 단골 질문이기도 합니다. 꼭 기억하세요!

  1. 성능 문제: System.out.println은 출력하는 동안 서버가 잠깐 멈춥니다(Blocking). 사용자가 몰리면 서버가 느려지는 주범이 됩니다.
  2. 정보 부족: 언제(시간), 어디서(클래스), 누가(스레드) 찍었는지 안 나옵니다. "hello" 라고만 찍히면 어디서 찍은 건지 찾을 수가 없습니다.
  3. 제어 불가: 운영 서버(배포)에서는 잡다한 로그를 끄고 에러만 보고 싶은데, sout은 일일이 코드를 지우지 않는 이상 끌 수가 없습니다. (로깅 라이브러리는 설정 파일 한 줄로 제어 가능)

 
 

반응형
Comments