Changelog¶
All notable changes to api-log-spring-boot-starter are documented here.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
Unreleased¶
3.0.1 — HTTP client fixes: Content-Type on body + PATCH method support¶
Two bugs in the HTTP client utilities (RestApiClientUtil /
ReactiveApiClientUtil) surfaced when the first real downstream consumer
(devslab-examples's api-log-*-demo set) exercised the POST/PUT/PATCH paths
through actual @RequestBody-annotated Spring controllers.
Fixed¶
Content-Typeheader was missing on POST/PUT/PATCH bodies. The utils serialised the body via Jackson then passed the resultingStringtoRestClient.body(String)/WebClient.bodyValue(String). Spring'sStringHttpMessageConverterthen wrote the body asContent-Type: text/plain;charset=ISO-8859-1(its default for raw String bodies), and downstream services that bind with@RequestBody Foorejected it as Unsupported Media Type. The fix setsapplication/jsonexplicitly in bothexchange()methods.patchSync*/patchAsync*was broken end-to-end. The auto-config registered aSimpleClientHttpRequestFactory(backed byjava.net.HttpURLConnection), whosesetRequestMethodthrowsProtocolException: Invalid HTTP method: PATCH— a long-standing JDK limitation. Swapped toJdkClientHttpRequestFactory(backed byjava.net.http.HttpClient, Java 11+) which supports all five HTTP verbs. Read-timeout property preserved; connect-timeout default is left toHttpClient's built-in.
Added — End-to-end integration test coverage¶
core/src/test/java/.../util/ now has four new test classes (65 cases
total):
RestApiClientUtilWireIT/ReactiveApiClientUtilWireIT— MockWebServer-driven wire-level assertions. VerifyContent-Typeon every body-carrying verb, exact body bytes, UTF-8 encoding (Korean + emoji round trip), large bodies (32 KB), HTTP method propagation, and that internal fields (e.g.ApiRequest.requestId) don't leak into wire headers.RestApiClientUtilSpringE2EIT/ReactiveApiClientUtilSpringE2EIT— Real@SpringBootTestwith@RestControllerdeclaring@RequestBody Foohandlers, on Tomcat / reactor-netty respectively. Each pinsspring.main.web-application-typebecause the test classpath has both starters. Covers all five verbs + 4xx/5xx propagation + Unicode/nested/ null-field round-trips.
The existing RestApiClientUtilRoutingTest /
ReactiveApiClientUtilRoutingTest (subclass-based, no real HTTP) couldn't
catch either of these bugs because they never reached the network layer. The
new IT classes close that gap and act as regression coverage for any future
HTTP-client refactor.
Compatibility¶
- No API changes. All
RestApiClientUtil/ReactiveApiClientUtilmethod signatures unchanged. Strict drop-in upgrade from3.0.0. - Behaviour change for raw non-JSON String bodies. Before
3.0.1, raw String payloads went out astext/plain. After3.0.1, all body-carrying calls sendapplication/json. If you genuinely need a different content type for an outbound call, use Spring'sRestClient/WebClientdirectly — api-log's wrappers are explicitly JSON-only by design (the whole library is JSON + JSONB-centric). ClientHttpRequestFactorybean swap. Any consumer that supplied their ownClientHttpRequestFactoryvia@ConditionalOnMissingBeancontinues to win; only the default factory changed.
Upgrading from 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")
Recommended for everyone on 3.0.0 — any consumer that calls a body-carrying
method against a real Spring controller is affected.
3.0.0 — Spring-major-aligned versioning policy¶
Renumbering of 0.6.0 per the new Spring-major-aligned versioning policy. No API, behaviour, or dependency changes — the major number bumps from 0.6 to 3.0 to match the Spring Boot major this line targets (Spring Boot 3). Published JAR bytes are identical to 0.6.0 apart from the version coordinate.
Going forward, all Spring Boot 3 releases of api-log ship on the 3.x.y line. The previous 0.6.0 artifacts remain on Maven Central as historical references.
Upgrading from 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")
No other changes. Same ApiLogWriter SPI, same auto-configuration shape.
0.6.0 — Multi-module split (Gradle), pluggable JPA / R2DBC / MyBatis backends¶
Changed¶
- The single
api-log-spring-boot-starterartifact is gone. The starter is now a multi-module Gradle build. Consumers addapi-log-coreplus exactly one backend artifact:
| Artifact (Maven coordinate) | What it provides |
|---|---|
kr.devslab:api-log-core |
Events, SPI, async listener, HTTP client utilities |
kr.devslab:api-log-jpa |
JPA + Hibernate persistence (the v0.5.x behavior) |
kr.devslab:api-log-r2dbc |
Reactive R2DBC persistence — no JDBC dependency |
kr.devslab:api-log-mybatis |
MyBatis mapper persistence |
Adding api-log-jpa is the closest drop-in for v0.5.x users; it pulls api-log-core transitively.
-
Build system: Maven → Gradle 8.10. Adopting easy-paging's convention: Vanniktech maven-publish per module, configuration cache disabled in CI to play nice with the publishing plugin. The Maven build files are gone —
./gradlew buildis the only path now. -
Package renames to reflect the layout:
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(now lives inapi-log-jpa)kr.devslab.apilog.repository.ApiLogRepository→kr.devslab.apilog.jpa.repository.ApiLogRepositorykr.devslab.apilog.service.ApiLogService→ replaced bykr.devslab.apilog.jpa.writer.JpaApiLogWriter(implements the newApiLogWriterSPI)
Added¶
ApiLogWriterSPI (kr.devslab.apilog.spi.ApiLogWriter) — three-method interface that every backend implements (writeInitiated,writeSuccess,writeError). The core listener routes events through whatever writer bean the consumer's backend artifact registered.api-log-r2dbc— reactive backend that talks to PostgreSQL via R2DBC'sDatabaseClient. JSONB binding uses the R2DBC PostgreSQL driver's implicitTEXT → JSONBcast, no manual::jsonbneeded. Ships a pure-reactive schema initializer (R2dbcScriptDatabaseInitializer) — zero JDBC pull-in.api-log-mybatis— MyBatis backend with a@Mapper-annotated interface. JSONB columns useCAST(#{...,jdbcType=VARCHAR} AS jsonb)in the@InsertSQL so no customTypeHandleris required.- Shared SPI helpers in
:core: HttpErrorExtractor— pulls HTTP status + body off thrown exceptions (was inline in the oldApiLogService).PayloadJsonMapper— JSON string /JsonNodeconversion used by every writer.
Fixed¶
V1.0__create_api_log.sqlis now idempotent. BothCREATE TABLEandCREATE INDEXgotIF NOT EXISTS. Previously the second boot under BUILTIN mode could fail with "relation already exists" if Hibernate'sddl-autowasn't catching it.
Migration from v0.5.2¶
Update your dependency coordinates:
<!-- 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 backend (drop-in for existing setups) -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-jpa</artifactId>
<version>0.6.0</version>
</dependency>
If you import any of the moved types directly, update the package:
// 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;
Reactive (R2DBC) or MyBatis adopters: swap api-log-jpa for api-log-r2dbc / api-log-mybatis instead — same ApiLogWriter contract, same api_log table.
0.5.2 — Fix bean registration in real consumer apps¶
Fixed¶
RestApiClientUtil,AsyncConfig,JacksonConfig,RestClientConfigwere never registered in consumer apps. They relied on the consumer's@ComponentScanreaching thekr.devslab.apilogpackage, which it doesn't for apps with a different base package. The starter's tests passed only because the test'sTestAppsat at the package root and scanned everything. In real usage:@Autowired RestApiClientUtilwould throwNoSuchBeanDefinitionException- The Blackbird-equipped
ObjectMapperwas missing — Spring Boot's default was used - The custom async executor was missing — Spring Boot's default was used
RestClientwith the configured timeouts and message converters was missing
- Fix: split
ApiLogAutoConfigurationinto three@AutoConfigurationclasses, register all inMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3 discovery):ApiLogAutoConfiguration— core (event listener, service, schema initializer, async executor,ObjectMapperwith Blackbird, retry config)RestApiClientAutoConfiguration— blocking HTTP, gated by@ConditionalOnClass(RestClient.class)(registersRestClient,MappingJackson2HttpMessageConverter,ClientHttpRequestFactory,RestApiClientUtil)ReactiveApiClientAutoConfiguration— reactive HTTP, gated by@ConditionalOnClass(WebClient.class)(registersReactiveApiClientUtilfrom the auto-configuredWebClient.Builder)
RestApiClientUtilis no longer annotated@Component. The auto-config's@Bean(with@ConditionalOnMissingBean) registers it.
Changed¶
spring-boot-starter-webis now<optional>true</optional>. Pure-WebFlux apps no longer get a Servlet stack forced onto their classpath. Consumers who want the blockingRestApiClientUtiladdspring-boot-starter-web(or justspring-web) themselves — most Servlet apps already have it.- Same pattern Spring's own optional integrations follow, and matches easy-paging-spring-boot-starter's compileOnly approach.
Removed (internal cleanup)¶
- The unused
RestTemplatebean thatRestClientConfigaccidentally exposed. UseRestClient(Spring 6+ recommended) or declare your own. - Stand-alone
AsyncConfig.java/JacksonConfig.java/RestClientConfig.javafiles. Their content moved into the auto-configurations.
Migration from v0.5.1¶
- If you depended on the (broken)
@ComponentScanworking — you weren't really gettingRestApiClientUtil. Now it'll appear once your build resolves successfully. - If you have a pure WebFlux app and weren't using the blocking client, the optional
spring-boot-starter-webchange means Tomcat etc. no longer ship transitively. Cleaner. - If you were quietly relying on the
RestTemplatebean — declare your own; it's no longer registered.
0.5.1 — Reactive (WebFlux) client + end-to-end HTTP tests¶
Added¶
ReactiveApiClientUtil—WebClient-backed reactive client. Same method surface asRestApiClientUtil(all 5 verbs × raw / typed +send()/sendTyped()cores) but returnsMono<ApiResponse>/Mono<T>. Same event-publishing contract, sameapi_logrows. Auto-registered viaReactiveApiClientConfigwhenspring-webfluxis on the classpath. See the new Reactive guide.RestApiClientUtilHttpIntegrationTestandReactiveApiClientUtilHttpIntegrationTest— end-to-end coverage usingMockWebServer+ Testcontainers PostgreSQL. Real HTTP traffic through both clients, real DB inserts via the async listener, then assertions on theapi_logrows. Closes the gap where v0.4.0's hardcoded-status / unstructured-error_message bugs could ship undetected.ReactiveApiClientUtilRoutingTest— fast mock-based unit tests for reactive verb routing.
Fixed¶
ApiLogService.saveApiCallErrornow extractsstatus_codeandresponseBodyfrom Spring WebFlux'sWebClientResponseException(a separate class hierarchy fromHttpStatusCodeException). Previously reactive 4xx/5xx ERROR rows hadstatus_code = NULL. Done via reflective duck-typing so consumers who don't pull inspring-webfluxaren't affected.
Dependency notes¶
spring-webfluxandreactor-netty-httpdeclared<optional>true</optional>— consumers who don't want the reactive client pay nothing.- Test scope:
com.squareup.okhttp3:mockwebserver4.12.0,io.projectreactor:reactor-test.
Migration from v0.5.0¶
Backward-compatible — all v0.5.0 APIs preserved. To use the reactive client, add spring-webflux + reactor-netty-http to your dependencies.
0.5.0 — PUT / DELETE / PATCH + retry-correlation via core API¶
Added¶
- PUT / DELETE / PATCH convenience methods on
RestApiClientUtil— 12 new methods following the existing GET/POST patterns:putSync,putSyncTyped,putAsync,putAsyncTyped,deleteSync,deleteSyncTyped,deleteAsync,deleteAsyncTyped,patchSync,patchSyncTyped,patchAsync,patchAsyncTyped(each with the appropriateString/ typed-body / typed-response overloads). - Core
send/sendAsync/sendTyped/sendAsyncTypedAPI taking(HttpMethod, ApiRequest)directly. Lets callers supply an explicitrequestIdso retry attempts share a correlation key — fills the gap documented in the v0.4.0 retry-handling guide.
Changed (internal — no public API change)¶
RestApiClientUtilrefactored so all 22 public methods funnel through the four coresend*methods. ~270 lines of duplicated try/catch/event-publish boilerplate collapsed into one place. Behavior identical to v0.4.0.
Tests¶
- New
RestApiClientUtilRoutingTestverifies each verb method routes through the correctHttpMethodand thatsend(HttpMethod, ApiRequest)respects a caller-providedrequestId.
Migration from v0.4.0¶
Fully backward-compatible — all v0.4.0 method signatures and behaviors are preserved. The new methods are additive.
0.4.0 — Bug fixes: real status codes, structured errors, honest retry docs¶
Fixed¶
RestApiClientUtilraw methods now report the actual HTTP status code.getSync,postSync(String, String),getAsync,postAsync(String, String)were hardcodingstatusCode = 200regardless of the real response (201, 204, etc. all stored as 200). The typed methods (*Typed) were never affected. Internally switched from.body(String.class)to.toEntity(String.class).ApiLogService.saveApiCallErrornow extracts HTTP status from Spring exceptions.status_codewas always NULL on ERROR / RETRY_ERROR rows. Now lifted offHttpStatusCodeException/RestClientResponseException.error_messageJSONB column now matches the documented shape. Previously stored astoJsonNode(message)— just the raw exception message string — which contradicted docs that promised{type, message}. Now writes structured{"type": "<fqcn>", "message": "<message>" [, "responseBody": "<upstream body>"]}.
Docs¶
retry-handlingguide rewritten to be accurate:RestApiClientUtildoes NOT propagate retry context (each retry gets a newrequest_id); the supported path for retry-timeline tracking is manual event publishing. Added a section onApiEventListener's own@Retryablelog-write retries.error_messagereference updated to document the newresponseBodyfield (only present for HTTP exceptions carrying a body).- README schema columns corrected:
event_type VARCHAR(50)(was 20),request_id VARCHAR(36)(was 255). - Removed misleading "Production-tested" / "Spring Retry integration with RETRY_ERROR events" claims.
- Maven Central and CI badges added to READMEs.
Migration from v0.3.0¶
Mostly backward-compatible — same API. Watch for:
status_codecolumn on raw-method SUCCESS rows: was always 200, now reflects reality (201, 204, etc.). Any query that hardcodedstatus_code = 200may need adjustment.error_messageJSONB shape: was a raw message string or{"raw": "..."}fallback. Now structured{type, message, responseBody?}. Queries againsterror_message ->> 'raw'no longer match — useerror_message ->> 'message'.status_codecolumn on ERROR rows: was always NULL. Now lifted from Spring exceptions when applicable (NULL for non-HTTP exceptions like timeouts).
0.3.0 — BUILTIN schema management is the new default¶
Changed¶
- BUILTIN is now the default schema management strategy. v0.2.0 made schema management opt-in with
NONEas the default, but that left users without Flyway/Liquibase staring at a "table doesn't exist" error on first boot. v0.3.0 flips the default toBUILTIN— the starter runsCREATE TABLE IF NOT EXISTSautomatically via Spring Boot'sDataSourceScriptDatabaseInitializer, so the table just exists. V1.0__create_api_log.sqlnow usesIF NOT EXISTSclauses, making it idempotent and safe to re-run on every boot. Flyway is fine with this; the version row inflyway_schema_historyis what matters, not whether the SQL did anything.
Strategy summary (after v0.3.0)¶
api.log.schema.management=builtin(default) — starter creates the table on startupapi.log.schema.management=flyway— starter registers aFlywayConfigurationCustomizer(requires Flyway on classpath)api.log.schema.management=none— starter does not touch the schema; you apply the DDL yourself
Migration from v0.2.0¶
- You had
management=flywayset explicitly: no change needed. - You had
management=noneset explicitly: no change needed. - You did not set
management(relying on the v0.2.0 defaultNONEand applying the DDL elsewhere): either setmanagement=noneexplicitly to preserve old behavior, or let BUILTIN take over (it's idempotent, so it won't fight your existing table).
0.2.0 — Schema management opt-in¶
Changed¶
- BREAKING: schema management is now opt-in. v0.1.0 force-installed Flyway and auto-ran the bundled migration. v0.2.0 makes the consumer choose:
api.log.schema.management=none(default) — apply the DDL yourself (Liquibase, manualpsql, your own Flyway flow)api.log.schema.management=flyway— starter registers aFlywayConfigurationCustomizerthat adds its location to your Flyway setup
flyway-coreandflyway-database-postgresqlare now<optional>true</optional>— consumers who wantmanagement=flywaymust declare Flyway in their own build.- Migration relocated from
classpath:db/migration/V1.0__create_api_log.sqltoclasspath:db/api-log/V1.0__create_api_log.sqlso it no longer collides with the consumer's default Flyway location.
Migration guide (from v0.1.0)¶
If you were depending on v0.1.0's auto-migration:
- Add
org.flywaydb:flyway-core(andflyway-database-postgresqlruntime) to your dependencies. - Set
api.log.schema.management=flywayin your config.
If you'd rather apply the schema yourself (recommended for production):
- Copy the SQL from Schema reference into your migration tool of choice.
- Leave
api.log.schema.managementat its default (none).
0.1.0 — Initial release¶
First public release. Repackaged as a standalone Spring Boot starter.
Changed¶
- Restructured as standalone Spring Boot Starter library.
groupId:com.devs.lab→kr.devslabartifactId:api-log-starter→api-log-spring-boot-starter- Java package:
com.devs.lab.test.*→kr.devslab.apilog.*
- Removed demo application (
ApiLogApplication,compose.yaml,Dockerfile.postgres) — pure library. - Removed app-only dependencies:
spring-boot-devtools,spring-boot-docker-compose,spring-boot-starter-actuator. - Removed
spring-boot-maven-pluginfrom build (library, not executable jar).
Fixed¶
- Flyway migration path:
db.migration/→db/migration/(the dot version wasn't a standard Flyway location and never auto-applied).
Added¶
LICENSE(Apache 2.0) +NOTICE.- Full
pom.xmlmetadata (licenses, SCM, developers, organization) required for Maven Central publishing. - Bilingual README (English default +
README.ko.md). - This documentation site.
Highlights¶
- Async, event-driven API call logging via
ApplicationEventPublisher. RestApiClientUtilbundled HTTP client withGET/POST×sync/async×raw/typed.- PostgreSQL JSONB storage for request/response/error bodies.
RETRY_ERRORevent type for retry attempts (consumer publishes manually or via their own@Retryablewrapper).- Auto-configuration via
ApiLogAutoConfigurationwith@ConditionalOnMissingBeanoverrides. - comprehensive test suite covering services, repository, listener, and Testcontainers-backed PostgreSQL integration.