변경 이력¶
api-log-spring-boot-starter의 모든 주요 변경 사항을 기록합니다.
형식은 Keep a Changelog를 따르며, 본 프로젝트는 Semantic Versioning을 준수합니다.
Unreleased¶
3.0.1 — HTTP 클라이언트 버그 픽스: body의 Content-Type + PATCH 메서드 지원¶
HTTP 클라이언트 유틸 (RestApiClientUtil / ReactiveApiClientUtil)의 두 가지
버그가 첫 실제 컨슈머인 devslab-examples의 api-log-*-demo 셋이
@RequestBody 어노테이션된 Spring 컨트롤러를 통한 POST/PUT/PATCH 경로를
실행하면서 표면화됨.
수정¶
- POST/PUT/PATCH body에
Content-Type헤더 누락. 유틸이 body를 Jackson으로 직렬화한 후 결과String을RestClient.body(String)/WebClient.bodyValue(String)에 전달 — Spring의StringHttpMessageConverter가 raw String body의 기본값인Content-Type: text/plain;charset=ISO-8859-1로 내보냄. 다운스트림에서@RequestBody Foo로 바인딩하는 서비스가 Unsupported Media Type으로 거부. 양쪽exchange()메서드에서application/json명시. patchSync*/patchAsync*가 완전히 깨져 있었음. auto-config가SimpleClientHttpRequestFactory(java.net.HttpURLConnection기반) 등록 —setRequestMethod가ProtocolException: Invalid HTTP method: PATCH던짐 (오래된 JDK 한계).JdkClientHttpRequestFactory(java.net.http.HttpClient기반, Java 11+)로 교체, 5개 HTTP verb 모두 지원. read-timeout 속성 유지, connect-timeout 기본값은HttpClient내장 기본값에 위임.
추가 — End-to-end 통합 테스트 커버리지¶
core/src/test/java/.../util/에 4개 새 테스트 클래스 (총 65개 케이스):
RestApiClientUtilWireIT/ReactiveApiClientUtilWireIT— MockWebServer로 wire-level 검증. body-carrying verb별Content-Type, 정확한 body 바이트, UTF-8 인코딩 (한글 + 이모지 round-trip), 큰 body (32 KB), HTTP 메서드 전파, 내부 필드 (ApiRequest.requestId등)가 wire 헤더로 새지 않음 — 모두 검증.RestApiClientUtilSpringE2EIT/ReactiveApiClientUtilSpringE2EIT— 실제@SpringBootTest+@RequestBody Foo받는@RestController띄움. servlet (Tomcat) / reactor-netty 각각. 테스트 클래스패스에 두 starter 다 있으니spring.main.web-application-type을 명시 고정. 5개 verb 전체 + 4xx/5xx propagation + 유니코드/중첩 객체/null 필드 round-trip.
기존 RestApiClientUtilRoutingTest / ReactiveApiClientUtilRoutingTest
(subclass 기반, 실제 HTTP 안 함)는 두 버그 모두 못 잡았음 — 네트워크 layer에
닿지 않았기 때문. 새 IT 클래스들이 그 갭을 메우고 향후 HTTP 클라이언트
리팩토링에 대한 회귀 보장 역할.
호환성¶
- API 변경 없음.
RestApiClientUtil/ReactiveApiClientUtil메서드 시그니처 그대로.3.0.0에서 엄격한 drop-in 업그레이드. - raw non-JSON String body 동작 변경.
3.0.1전엔 raw String payload가text/plain으로 나갔음. 이제 모든 body-carrying 호출이application/json으로. 아웃바운드 호출에 다른 content type이 정말 필요하면 Spring의RestClient/WebClient를 직접 사용 — api-log 래퍼는 설계상 JSON 전용 (라이브러리 전체가 JSON + JSONB 중심). ClientHttpRequestFactorybean swap. 컨슈머가@ConditionalOnMissingBean으로 자체ClientHttpRequestFactory를 제공하면 그게 계속 우선; default factory만 바뀜.
3.0.0에서 올라오기¶
- implementation("kr.devslab:api-log-core:3.0.0")
+ implementation("kr.devslab:api-log-core:3.0.1")
- implementation("kr.devslab:api-log-jpa:3.0.0")
+ implementation("kr.devslab:api-log-jpa:3.0.1")
- implementation("kr.devslab:api-log-r2dbc:3.0.0")
+ implementation("kr.devslab:api-log-r2dbc:3.0.1")
- implementation("kr.devslab:api-log-mybatis:3.0.0")
+ implementation("kr.devslab:api-log-mybatis:3.0.1")
3.0.0 사용자 모두 권장 — 실제 Spring 컨트롤러로 body-carrying 메서드를
호출하는 모든 컨슈머에 영향.
3.0.0 — Spring-major 정렬 버전 정책¶
0.6.0의 재번호링 — 새 Spring-major 정렬 버전 정책에 따름. API / 동작 / 의존성 변경 전혀 없음 — 메이저 숫자를 0.6 → 3.0으로 올려서 이 라인의 타겟 Spring Boot 메이저 (Spring Boot 3)와 일치시킴. 발행된 JAR 바이트는 0.6.0과 동일 (POM의 버전 좌표만 다름).
앞으로 모든 Spring Boot 3 릴리즈는 3.x.y 라인. 기존 0.6.0 아티팩트는 historical reference로 Maven Central에 잔존.
0.6.0에서 올라오기¶
- implementation("kr.devslab:api-log-jpa:0.6.0")
+ implementation("kr.devslab:api-log-jpa:3.0.0")
- implementation("kr.devslab:api-log-r2dbc:0.6.0")
+ implementation("kr.devslab:api-log-r2dbc:3.0.0")
- implementation("kr.devslab:api-log-mybatis:0.6.0")
+ implementation("kr.devslab:api-log-mybatis:3.0.0")
다른 변경 없음. ApiLogWriter SPI 동일, 자동 설정 형태 동일.
0.6.0 — 멀티모듈 분리 (Gradle), JPA / R2DBC / MyBatis 백엔드 선택 지원¶
Changed¶
- 단일
api-log-spring-boot-starter아티팩트가 분리됐습니다. 이제는 멀티모듈 Gradle 빌드입니다. 사용자는api-log-core+ 백엔드 1개를 직접 골라 추가:
| 아티팩트 (Maven 좌표) | 역할 |
|---|---|
kr.devslab:api-log-core |
이벤트, SPI, async 리스너, HTTP 클라이언트 유틸 |
kr.devslab:api-log-jpa |
JPA + Hibernate 영속화 (v0.5.x 동작 그대로) |
kr.devslab:api-log-r2dbc |
리액티브 R2DBC 영속화 — JDBC 의존성 없음 |
kr.devslab:api-log-mybatis |
MyBatis mapper 영속화 |
v0.5.x에서 가장 비슷한 드롭인은 api-log-jpa입니다 (자동으로 api-log-core를 가져옴).
-
빌드 시스템: Maven → Gradle 8.10. easy-paging 컨벤션 적용 — 모듈마다 Vanniktech maven-publish, CI에서는 publish 플러그인의 property-driven 설정과 충돌하지 않도록 configuration cache 비활성화. Maven 파일은 사라졌고,
./gradlew build가 유일한 빌드 경로입니다. -
패키지 이름 변경 (구조 정리 차원):
kr.devslab.apilog.model.dto.ApiRequest→kr.devslab.apilog.dto.ApiRequestkr.devslab.apilog.model.dto.ApiResponse→kr.devslab.apilog.dto.ApiResponsekr.devslab.apilog.model.ApiLogEntity→kr.devslab.apilog.jpa.model.ApiLogEntity(api-log-jpa로 이동)kr.devslab.apilog.repository.ApiLogRepository→kr.devslab.apilog.jpa.repository.ApiLogRepositorykr.devslab.apilog.service.ApiLogService→kr.devslab.apilog.jpa.writer.JpaApiLogWriter로 대체 (새ApiLogWriterSPI 구현)
Added¶
ApiLogWriterSPI (kr.devslab.apilog.spi.ApiLogWriter) — 모든 백엔드가 구현하는 3-메서드 인터페이스 (writeInitiated,writeSuccess,writeError). 코어 리스너는 사용자가 추가한 백엔드 아티팩트가 등록한 writer 빈으로 이벤트를 라우팅합니다.api-log-r2dbc— R2DBC의DatabaseClient로 PostgreSQL과 통신하는 리액티브 백엔드. JSONB 바인딩은 R2DBC PostgreSQL 드라이버의 묵시적TEXT → JSONB캐스트를 활용, 별도의::jsonb작업이 필요 없음. 순수 리액티브 스키마 초기화 (R2dbcScriptDatabaseInitializer) 제공 — JDBC 끌어들이지 않음.api-log-mybatis—@Mapper인터페이스 기반 MyBatis 백엔드. JSONB 컬럼은CAST(#{...,jdbcType=VARCHAR} AS jsonb)구문으로 처리해서 별도의TypeHandler가 필요 없음.:core에서 공유하는 SPI 헬퍼:HttpErrorExtractor— 던져진 예외에서 HTTP 상태 / 본문 추출 (기존ApiLogService내부 로직 분리).PayloadJsonMapper— 모든 writer가 사용하는 JSON 문자열 /JsonNode변환.
Fixed¶
V1.0__create_api_log.sql이 이제 멱등합니다.CREATE TABLE과CREATE INDEX둘 다IF NOT EXISTS추가. 기존에는 BUILTIN 모드에서 두 번째 부팅 시 "relation already exists" 에러가 발생할 수 있었음 (Hibernateddl-auto가 잡아주지 않으면).
v0.5.2에서 마이그레이션¶
의존성 좌표 변경:
<!-- v0.5.x -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-spring-boot-starter</artifactId>
<version>0.5.2</version>
</dependency>
<!-- v0.6.0 — JPA 백엔드 (기존 사용자에게 가장 가까운 드롭인) -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-jpa</artifactId>
<version>0.6.0</version>
</dependency>
이동된 타입을 직접 import 하던 경우 패키지 업데이트:
// Before
import kr.devslab.apilog.model.dto.ApiRequest;
import kr.devslab.apilog.model.ApiLogEntity;
// After
import kr.devslab.apilog.dto.ApiRequest;
import kr.devslab.apilog.jpa.model.ApiLogEntity;
리액티브 (R2DBC) 또는 MyBatis로 전환하려는 경우: api-log-jpa 대신 api-log-r2dbc / api-log-mybatis만 바꿔 끼우면 됩니다 — 동일한 ApiLogWriter 계약, 동일한 api_log 테이블.
0.5.2 — 실제 consumer 앱에서 빈 등록 문제 픽스¶
Fixed¶
RestApiClientUtil,AsyncConfig,JacksonConfig,RestClientConfig가 실제 consumer 앱에서 등록되지 않던 버그. Consumer의@ComponentScan이kr.devslab.apilog패키지까지 닿아야 했는데, base package가 다른 앱들은 안 닿음. 스타터 테스트만 통과한 이유:TestApp이 패키지 루트에 있어서 전체 스캔. 실제 사용 시:@Autowired RestApiClientUtil→NoSuchBeanDefinitionException- Blackbird
ObjectMapper가 없었음 — Spring Boot 기본값 사용 - 커스텀 async executor가 없었음 — Spring Boot 기본값 사용
- 타임아웃·메시지 컨버터 설정된
RestClient가 없었음
- 픽스:
ApiLogAutoConfiguration을 3개의@AutoConfiguration으로 분리,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3 discovery)에 다 등록:ApiLogAutoConfiguration— 코어 (이벤트 리스너, 서비스, 스키마 초기화, async executor, BlackbirdObjectMapper, retry config)RestApiClientAutoConfiguration— 블로킹 HTTP,@ConditionalOnClass(RestClient.class)게이트 (RestClient,MappingJackson2HttpMessageConverter,ClientHttpRequestFactory,RestApiClientUtil등록)ReactiveApiClientAutoConfiguration— 리액티브 HTTP,@ConditionalOnClass(WebClient.class)게이트 (자동 구성된WebClient.Builder에서ReactiveApiClientUtil등록)
RestApiClientUtil에서@Component제거. Auto-config의@Bean(@ConditionalOnMissingBean)이 등록.
Changed¶
spring-boot-starter-web가<optional>true</optional>로 변경. 순수 WebFlux 앱에 Servlet 스택을 강제로 들이지 않음. 블로킹RestApiClientUtil을 쓰는 사용자는 직접spring-boot-starter-web(또는spring-web) 추가 — 대부분의 Servlet 앱은 이미 갖고 있음.- Spring 자체 옵셔널 통합과 같은 패턴이고, easy-paging-spring-boot-starter의 compileOnly 접근과 일치.
Removed (내부 정리)¶
RestClientConfig가 우연히 노출하던 미사용RestTemplate빈.RestClient(Spring 6+ 권장) 또는 본인이 직접 선언.- 독립
AsyncConfig.java/JacksonConfig.java/RestClientConfig.java파일. 내용이 auto-config들로 이동.
v0.5.1에서 마이그레이션¶
- (망가진)
@ComponentScan에 의존하고 있었다면 — 사실RestApiClientUtil을 제대로 못 받고 있었던 것. 빌드 resolve되면 이제 정상 등장. - 순수 WebFlux 앱이고 블로킹 클라이언트 안 썼다면, optional 변경으로 Tomcat 등이 더 이상 transitive로 안 옴. 깔끔.
- 조용히
RestTemplate빈에 의존하고 있었다면 — 본인이 직접 선언; 더 이상 등록 안 됨.
0.5.1 — 리액티브 (WebFlux) 클라이언트 + end-to-end HTTP 테스트¶
Added¶
ReactiveApiClientUtil—WebClient기반 리액티브 클라이언트.RestApiClientUtil과 동일한 메서드 표면 (5개 verb × raw / typed +send()/sendTyped()코어)이지만Mono<ApiResponse>/Mono<T>반환. 동일한 이벤트 발행 계약, 동일한api_log행.spring-webflux가 클래스패스에 있을 때ReactiveApiClientConfig가 자동 등록. 새 리액티브 가이드 참고.RestApiClientUtilHttpIntegrationTest+ReactiveApiClientUtilHttpIntegrationTest—MockWebServer+ Testcontainers PostgreSQL로 end-to-end 커버리지. 양쪽 클라이언트로 실제 HTTP 트래픽, 비동기 리스너로 실제 DB INSERT, 그 다음api_log행에 대한 단언.ReactiveApiClientUtilRoutingTest— 리액티브 verb 라우팅을 위한 빠른 mock 기반 단위 테스트.
Fixed¶
ApiLogService.saveApiCallError가 Spring WebFlux의WebClientResponseException(HttpStatusCodeException과 별개 계층)에서status_code와responseBody를 추출하도록 수정. 이전에는 리액티브 4xx/5xx ERROR 행의status_code = NULL이었음.spring-webflux를 안 가져오는 사용자에게 영향 없도록 리플렉션 duck-typing 사용.
의존성 노트¶
spring-webflux와reactor-netty-http는<optional>true</optional>선언 — 리액티브 클라이언트 안 쓰는 사용자는 무비용.- test scope:
com.squareup.okhttp3:mockwebserver4.12.0,io.projectreactor:reactor-test.
v0.5.0에서 마이그레이션¶
하위 호환 — v0.5.0의 모든 API 보존. 리액티브 클라이언트를 쓰려면 spring-webflux + reactor-netty-http를 의존성에 추가.
0.5.0 — PUT / DELETE / PATCH + 코어 API로 재시도 correlation¶
Added¶
RestApiClientUtil에 PUT / DELETE / PATCH 편의 메서드 — 기존 GET/POST 패턴을 따르는 12개 신규 메서드:putSync,putSyncTyped,putAsync,putAsyncTyped,deleteSync,deleteSyncTyped,deleteAsync,deleteAsyncTyped,patchSync,patchSyncTyped,patchAsync,patchAsyncTyped(각각String/ 타입 바디 / 타입 응답 오버로드 적절히).- 코어
send/sendAsync/sendTyped/sendAsyncTypedAPI —(HttpMethod, ApiRequest)를 직접 받음. 호출자가 명시적requestId를 전달해 재시도 시도들이 correlation 키를 공유 — v0.4.0 재시도 가이드에서 지적한 갭을 채움.
Changed (내부 — 공개 API 변경 없음)¶
RestApiClientUtil리팩토링: 22개 공개 메서드가 모두 코어 4개send*메서드로 흐름. try/catch/이벤트 발행 중복 코드 약 270줄이 한 곳으로 정리. 동작은 v0.4.0과 동일.
Tests¶
- 새
RestApiClientUtilRoutingTest— 각 동사 메서드가 올바른HttpMethod로 라우팅되고send(HttpMethod, ApiRequest)가 호출자 제공requestId를 존중하는지 검증.
v0.4.0에서 마이그레이션¶
완전히 하위 호환 — v0.4.0의 모든 메서드 시그니처와 동작 보존. 새 메서드는 추가만.
0.4.0 — 버그 픽스: 실제 상태 코드, 구조화된 에러, 정직해진 재시도 문서¶
Fixed¶
RestApiClientUtilraw 메서드가 실제 HTTP 상태 코드를 보고.getSync,postSync(String, String),getAsync,postAsync(String, String)이 실제 응답과 무관하게statusCode = 200하드코딩 (201, 204 다 200으로 저장). 타입 메서드(*Typed)는 영향 없었음. 내부적으로.body(String.class)→.toEntity(String.class)로 전환.ApiLogService.saveApiCallError가 Spring 예외에서 HTTP 상태 추출. ERROR / RETRY_ERROR 행의status_code는 항상 NULL이었음. 이제HttpStatusCodeException/RestClientResponseException에서 추출.error_messageJSONB 컬럼이 문서 형식과 일치. 이전엔toJsonNode(message)— 예외 메시지 문자열만 — 으로 저장되어 문서의{type, message}약속과 모순. 이제 구조화:{"type": "<FQCN>", "message": "<메시지>" [, "responseBody": "<업스트림 본문>"]}.
Docs¶
retry-handling가이드 재작성:RestApiClientUtil은 재시도 컨텍스트를 전파하지 않음(매 재시도마다 새request_id). 재시도 타임라인 추적의 지원 경로는 이벤트 직접 발행.ApiEventListener의@Retryable로그 쓰기 재시도 섹션 추가.error_message레퍼런스 업데이트: 새responseBody필드 문서화 (본문을 가진 HTTP 예외에만 존재).- README 스키마 컬럼 정정:
event_type VARCHAR(50)(이전 20),request_id VARCHAR(36)(이전 255). - "Production-tested" / "Spring Retry integration with RETRY_ERROR events" 잘못된 주장 제거.
- Maven Central + CI 배지 READMEs에 추가.
v0.3.0에서 마이그레이션¶
대부분 호환 — 같은 API. 주의:
- raw 메서드 SUCCESS 행의
status_code: 항상 200이었음, 이제 실제 반영 (201, 204 등).status_code = 200을 하드코딩한 쿼리는 조정 필요. error_messageJSONB 형식: 원시 메시지 문자열 또는{"raw": "..."}fallback이었음. 이제 구조화{type, message, responseBody?}.error_message ->> 'raw'쿼리는 더 이상 매치 안 됨 —error_message ->> 'message'사용.- ERROR 행의
status_code: 항상 NULL이었음. 이제 Spring 예외에서 추출 (타임아웃 같은 비-HTTP 예외는 NULL).
0.3.0 — BUILTIN 스키마 관리가 새 기본값¶
Changed¶
- BUILTIN이 새 기본 스키마 관리 전략. v0.2.0이 스키마 관리를 옵트인(
NONE기본)으로 만들었지만, Flyway/Liquibase 안 쓰는 사용자는 첫 부팅에서 "테이블 없음" 에러를 봐야 했습니다. v0.3.0은 기본값을BUILTIN으로 뒤집어 — 스타터가 Spring Boot의DataSourceScriptDatabaseInitializer로CREATE TABLE IF NOT EXISTS를 자동 실행. 테이블이 그냥 존재합니다. V1.0__create_api_log.sql이IF NOT EXISTS절을 사용해 멱등적 — 매 부팅마다 안전하게 재실행 가능. Flyway도 문제 없음 (flyway_schema_history행이 핵심, SQL 결과가 아님).
전략 정리 (v0.3.0 이후)¶
api.log.schema.management=builtin(기본) — 스타터가 부팅 시 테이블 생성api.log.schema.management=flyway— 스타터가FlywayConfigurationCustomizer등록 (Flyway 의존성 필요)api.log.schema.management=none— 스타터가 스키마에 손 안 댐; 사용자가 직접 DDL 적용
v0.2.0에서 마이그레이션¶
management=flyway명시한 경우: 변경 불필요.management=none명시한 경우: 변경 불필요.management를 설정 안 한 경우 (v0.2.0 기본 NONE에 의존, 다른 곳에서 DDL 적용 중):management=none을 명시해서 기존 동작 유지하거나, BUILTIN이 동작하도록 그대로 두기 (멱등적이라 기존 테이블과 충돌 없음).
0.2.0 — 스키마 관리 옵트인¶
Changed¶
- BREAKING: 스키마 관리가 옵트인 방식으로 변경. v0.1.0은 Flyway를 강제로 깔고 번들 마이그레이션을 자동 실행했습니다. v0.2.0부터는 사용자가 선택:
api.log.schema.management=none(기본) — DDL 직접 적용 (Liquibase, 수동psql, 자체 Flyway 흐름)api.log.schema.management=flyway— 스타터가FlywayConfigurationCustomizer를 등록해 자기 마이그레이션 위치를 Flyway에 추가
flyway-core,flyway-database-postgresql이<optional>true</optional>로 변경 —management=flyway쓰는 사용자가 직접 Flyway 의존성 추가 필요.- 마이그레이션 위치 이동:
classpath:db/migration/V1.0__create_api_log.sql→classpath:db/api-log/V1.0__create_api_log.sql(사용자 기본 Flyway 위치와 충돌 방지).
v0.1.0에서 마이그레이션¶
v0.1.0의 자동 마이그레이션에 의존하고 있었다면:
org.flywaydb:flyway-core(+ runtime의flyway-database-postgresql)를 본인 의존성에 추가.- 설정에
api.log.schema.management=flyway추가.
스키마를 직접 적용하고 싶다면 (운영 환경 권장):
- 스키마 레퍼런스의 SQL을 본인 마이그레이션 도구에 복사.
api.log.schema.management는 기본값(none) 그대로.
0.1.0 — 첫 공개¶
첫 공개 릴리스. 독립 Spring Boot starter로 재패키징.
Changed¶
- 독립 Spring Boot Starter 라이브러리로 재구성.
groupId:com.devs.lab→kr.devslabartifactId:api-log-starter→api-log-spring-boot-starter- Java 패키지:
com.devs.lab.test.*→kr.devslab.apilog.*
- 데모 애플리케이션 제거 (
ApiLogApplication,compose.yaml,Dockerfile.postgres) — 순수 라이브러리. - 앱 전용 의존성 제거:
spring-boot-devtools,spring-boot-docker-compose,spring-boot-starter-actuator. - 빌드에서
spring-boot-maven-plugin제거 (라이브러리, 실행 jar 아님).
Fixed¶
- Flyway 마이그레이션 경로:
db.migration/→db/migration/(점 버전은 표준 Flyway 위치가 아니라 자동 적용되지 않았음).
Added¶
LICENSE(Apache 2.0) +NOTICE.- Maven Central 발행에 필요한
pom.xml메타데이터 (licenses, SCM, developers, organization). - 양국어 README (영문 기본 +
README.ko.md). - 본 문서 사이트.
하이라이트¶
ApplicationEventPublisher를 통한 비동기, 이벤트 드리븐 API 호출 로깅.RestApiClientUtil내장 HTTP 클라이언트 —GET/POST×sync/async×raw/typed.- 요청/응답/에러 본문의 PostgreSQL JSONB 저장.
- 재시도 시도를 위한
RETRY_ERROR이벤트 타입 (사용자가 직접 발행하거나 본인의@Retryable래퍼를 통해). ApiLogAutoConfiguration을 통한 자동 구성,@ConditionalOnMissingBean오버라이드.- 서비스·리포지토리·리스너·Testcontainers 기반 PostgreSQL 통합까지 포괄적 테스트.