[Spring Boot] Logbook으로 HTTP 요청/응답 로깅하기

왔다 정보리·2026년 2월 16일

API를 개발하다 보면 HTTP 요청과 응답을 로깅해야 할 때가 있다. 단순히 log.info()로 남기기에는 매번 코드에 직접 작성해야 하고, 민감 정보 마스킹이나 환경별 설정 분리까지 고려하면 상당히 번거로워진다. 이를 해결할 수 있는 방법을 찾다가 Zalando에서 만든 Logbook이라는 라이브러리를 알게 되었고, 설정만으로 HTTP 통신을 자동으로 로깅할 수 있었다. Logbook에 대해 정리하고자 한다!

Logbook 예시

Logbook이란?


Logbook

https://github.com/zalando/logbook

Zalando에서 만든 HTTP 요청/응답 전용 로깅 라이브러리이다. 일반 로깅과 달리 HTTP 요청/응답에 특화되어 있어 요청과 응답을 분리해서 로깅할 수 있다. 설정만 하면 모든 HTTP 통신이 자동으로 로깅되며, REST API, WebClient, RestTemplate, Feign 등과 연동이 가능하다.

Logbook의 장점

  1. 유연한 로깅 전략 : 특정 상태 코드, URL 패턴 등의 조건부 로깅이 가능하고, 성능을 위한 비동기 로깅을 지원한다.
  2. 다양한 출력 포맷 지원 : HTTP, JSON, CURL 및 커스텀 포맷터를 지원한다.
  3. 보안 및 프라이버시 : 민감한 정보를 자동으로 마스킹하고, 헤더나 바디에 대한 커스텀 마스킹 규칙을 설정할 수 있다.

주요 컴포넌트

컴포넌트역할상세
Condition어떤 요청을 로깅할지 결정URL 패턴, HTTP 메서드, 헤더 기반 필터링
Sink로그를 실제로 출력SLF4J, Apache Commons Logging 등과 연동
Strategy요청/응답 처리 방식을 결정default, status-at-least, body-only-if-status-at-least, without-body

Strategy 종류

Strategy설명
default모든 요청/응답 로깅
status-at-least특정 상태 코드 이상만 로깅
body-only-if-status-at-least특정 상태 코드 이상일 때만 body 포함
without-bodybody 제외하고 로깅

Logbook 로깅 단계

Logbook은 요청이 들어오면 다음 4단계를 거쳐 로깅을 수행한다.

  1. Conditional Phase : 특정 요청이 로깅될지 여부를 결정한다. 헬스 체크 등의 특정 요청을 제외하도록 조건을 설정할 수 있다.
  2. Filtering Phase : 로깅하기 전에 요청 데이터를 수정한다. 민감한 정보를 제거하거나 특정 요소들이 표시되는 방식을 커스터마이징 할 수 있다.
  3. Formatting Phase : 요청과 응답 데이터를 HTTP, JSON 등 특정 형식으로 가공한다.
  4. Writing Phase : 포맷된 로그 메세지를 파일, 콘솔, 또는 외부 시스템에 출력한다.

Logbook 주요 설정


설정 항목

설정설명
logbook.filter.enabledLogbookFilter 사용 여부
logbook.format.style로그 출력 형식 (http, json, curl, splunk)
logbook.minimum-status특정 HTTP 상태 코드 이상의 응답부터 로깅 ex) 400 설정 시 4xx, 5xx만 로깅
logbook.obfuscate.headers마스킹할 헤더 목록 ex) Authorization, Cookie
logbook.obfuscate.parameters마스킹할 URL 쿼리 파라미터 목록 ex) password, access_token
logbook.obfuscate.json-body-fields마스킹할 JSON 바디 필드 목록 ex) password, email
logbook.obfuscate.replacement마스킹된 부분에 표시할 문자 (기본값 XXX)
logbook.predicate.include특정 경로만 로깅
logbook.predicate.exclude특정 경로는 로깅에서 제외
logbook.write.chunk-size로그를 작은 조각으로 나눌 크기 (기본 8192byte)
logbook.write.max-body-size바디가 너무 클 때 잘라낼 최대 글자수 (기본 8192byte)

설정 시 주의사항

  • include vs exclude : exclude 설정이 include 설정보다 우선적으로 적용된다.
  • filter.enabled : false로 설정하면 모든 로깅 필터가 비활성화된다.
  • secure-filter.enabled : false로 설정하면 obfuscate 마스킹이 무시된다.

마스킹(obfuscate) 범위 정리

마스킹 대상에 따라 설정 위치가 다르다. 각 설정의 대상과 예시를 정리하면 다음과 같다.

설정대상예시
obfuscate.headersHTTP 헤더 값Authorization: ***
obfuscate.parametersURL 쿼리 파라미터 값?password=***
obfuscate.json-body-fieldsJSON 바디의 필드 값 (yml){"password":"***"}
BodyFilter BeanJSON 바디의 필드 값 (Java){"email":"***"}

obfuscate.json-body-fields는 yml 설정만으로 간단히 적용 가능하지만, logbook-json 모듈의 JsonBodyFilters를 사용한 BodyFilter Bean 등록 방식이 더 세밀한 제어가 가능하다.

Logbook 적용하기


1. 의존성 설정

// Logbook - HTTP 요청/응답 로깅
implementation 'org.zalando:logbook-spring-boot-starter:3.9.0'
implementation 'org.zalando:logbook-json:3.9.0'

의존성 설명

의존성설명
logbook-spring-boot-starterSpring Boot 자동 설정 및 기본 로깅 기능을 제공한다
logbook-jsonJsonBodyFilters 등 JSON 바디 필터링 기능을 제공한다. logbook-spring-boot-starter에 자동으로 포함되지 않으므로 명시적으로 추가해야 한다

2. application-log.yml 작성

logbook:
  filter:
    enabled: true
  obfuscate:
    replacement: "***"
    headers:
      - Authorization
      - Cookie
      - Set-Cookie
    parameters:
      - password
      - access_token
      - refresh_token
      - code

---
spring:
  config:
    activate:
      on-profile: local

logbook:
  format:
    style: http
  predicate:
    exclude:
      - path: /actuator/**
      - path: /swagger-ui/**
      - path: /v3/api-docs/**
      - path: /swagger-resources/**
      - path: /favicon.ico

logging:
  level:
    org.zalando.logbook: TRACE

---
spring:
  config:
    activate:
      on-profile: dev

logbook:
  format:
    style: json
  predicate:
    exclude:
      - path: /actuator/**
      - path: /swagger-ui/**
      - path: /v3/api-docs/**
      - path: /swagger-resources/**
      - path: /favicon.ico

logging:
  level:
    org.zalando.logbook: TRACE

---
spring:
  config:
    activate:
      on-profile: prod

logbook:
  format:
    style: json
  minimum-status: 400
  write:
    max-body-size: 2048
  predicate:
    exclude:
      - path: /actuator/**
      - path: /swagger-ui/**
      - path: /v3/api-docs/**
      - path: /swagger-resources/**
      - path: /favicon.ico
      - path: /excel-upload/**

logging:
  level:
    org.zalando.logbook: TRACE

공통 영역에 마스킹 설정을 두어 모든 프로필에서 동일하게 적용되도록 하고, 각 프로필별로 포맷과 로깅 범위를 다르게 설정하였다.

환경별 설정 설명

환경포맷로깅 범위특이사항
공통 (default)-전체마스킹 설정 (헤더, 파라미터)
localhttp전체가독성 좋은 HTTP 포맷
devjson전체구조화된 JSON 포맷
prodjson400 이상만minimum-status: 400, max-body-size: 2048 제한

logging.level.org.zalando.logbook: TRACE 설정이 있어야 HTTP 요청/응답 로깅이 동작한다.
prod 환경에서 minimum-status: 400을 설정하면 정상 응답(2xx, 3xx)은 로깅하지 않아 성능 부담을 줄일 수 있다. 또한, prod 환경에서 max-body-size: 2048을 설정하면 대용량 응답 바디가 잘려서 로깅되어 로그 저장 비용을 절감할 수 있다.

3. LogbookConfig 작성

@Configuration
public class LogbookConfig {

    @Bean
    public BodyFilter bodyFilter() {
        return JsonBodyFilters.replaceJsonStringProperty(
                Set.of(
                        "password",
                        "accessToken", "refreshToken",
                        "code",
                        "email"
                ),
                "***"
        );
    }
}

Logbook 전체를 Bean으로 등록하지 않고, BodyFilter만 Bean으로 등록하면 yml 설정과 커스텀 로직을 동시에 사용할 수 있다. JsonBodyFilters.replaceJsonStringProperty()logbook-json 모듈에 포함되어 있으며, JSON 바디 내 지정된 필드 값을 ***로 마스킹한다.

주의사항

Logbook 전체를 Bean으로 만들면 (Logbook.builder()...build()) application.yml에 정의한 설정이 모두 무시된다. 커스텀 로직이 필요한 컴포넌트(BodyFilter, HttpLogWriter 등)만 개별 Bean으로 등록해야 yml 설정과 공존할 수 있다.

마스킹 설정 구분

마스킹 대상설정 위치설정 방법
HTTP 헤더 (Authorization, Cookie 등)ymlobfuscate.headers
URL 쿼리 파라미터 (password, token 등)ymlobfuscate.parameters
JSON 바디 필드 (password, email 등)Java ConfigBodyFilter Bean + JsonBodyFilters

실행 결과


http 포맷 (local 환경) - 요청

2026-02-16T18:15:56.915+09:00 TRACE 79267 --- [room-in-us] [nio-8090-exec-1] org.zalando.logbook.Logbook              : Incoming Request: c3f73321e96883eb
Remote: 0:0:0:0:0:0:0:1
POST http://localhost:8090/login HTTP/1.1
accept: */*
accept-encoding: gzip, deflate, br, zstd
accept-language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
connection: keep-alive
content-length: 65
content-type: application/json
cookie: ***
host: localhost:8090
origin: http://localhost:8090
referer: http://localhost:8090/api/swagger-ui/index.html
sec-ch-ua: "Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36

{"email":"***","password":"***"}

요청의 cookie 헤더가 ***로 마스킹되고, 바디의 email, password 필드도 ***로 마스킹된 것을 확인할 수 있다.

http 포맷 (local 환경) - 응답

2026-02-16T18:15:57.255+09:00 TRACE 79267 --- [room-in-us] [nio-8090-exec-1] org.zalando.logbook.Logbook              : Outgoing Response: c3f73321e96883eb
Duration: 346 ms
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Type: application/json
Date: Mon, 16 Feb 2026 09:15:57 GMT
Expires: 0
Keep-Alive: timeout=60
Pragma: no-cache
Set-Cookie: ***, ***
Transfer-Encoding: chunked
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0

{"memberId":1,"accessToken":"***","refreshToken":"***"}

응답 헤더의 Set-Cookie와 바디의 accessToken, refreshToken 필드가 ***로 마스킹된 것을 확인할 수 있다.

마치며


Logbook을 통해 설정만으로 HTTP 요청/응답을 자동으로 로깅할 수 있었다. 특히 환경별로 로깅 전략을 다르게 가져갈 수 있어, local에서는 가독성 좋은 HTTP 포맷으로 전체 로깅을 하고, prod에서는 JSON 포맷으로 에러 응답만 로깅하는 식으로 분리할 수 있었다. 라이브러리를 통해 편하게 로그를 관리할 수 있다는 점이 큰 장점으로 느껴졌다!

참고 자료

Logbook - GitHub
Introducing Zalando Logbook and How to Integrate It with Spring Boot - NashTech Blog

profile
왔다 정보리

0개의 댓글