English · 한국어
Spring Boot용 이벤트 드리븐 API 호출 로깅. 비동기 이벤트 파이프라인 + PostgreSQL JSONB. 요청 경로를 막지 않고 외부 API 호출을 모두 기록합니다.
서비스의 모든 외부 HTTP 호출 — 요청, 응답, 에러, 재시도 — 을 PostgreSQL에 기록합니다. 논블로킹 이벤트 파이프라인으로 동작하므로 호출자는 로그 쓰기를 기다리지 않습니다. 번들된 RestApiClientUtil로 보낸 호출이든, 직접 발행하는 이벤트든, JSONB로 저장됩니다.
@Service
public class UserService {
private final RestApiClientUtil api;
public UserService(RestApiClientUtil api) {
this.api = api;
}
public User createUser(User newUser) {
// HTTP 호출은 동기적이고; 로깅 이벤트는 백그라운드에서 발화.
return api.postSyncTyped("/api/users", newUser, User.class);
}
}한 호출이 api_log에 한 행 이상 생성:
- INITIATED — 요청 발사
- SUCCESS / ERROR — 종료 결과 (상태 코드 + 페이로드)
- RETRY_ERROR — 재시도 실패 시 (사용자가 직접 발행)
본문은 JSONB로 저장되어 ->, ->>, GIN 인덱스로 자유롭게 조회 가능.
- 논블로킹 — 로그 쓰기는 별도 스레드. HTTP 호출 경로는 절대 막히지 않음.
- PostgreSQL JSONB — 요청·응답·에러 본문이 조회 가능한 JSON
- 재시도 인식 스키마 —
RETRY_ERROR이벤트 +retry_count/is_retry컬럼. 리스너도 일시적 DB 실패에 대해 로그 쓰기를 3회 재시도. - Virtual Threads 지원 — Java 21+ 비동기 설계
- Drop-in 스타터 — 자동 구성으로 모든 빈 등록. 직접 빈 정의하면 오버라이드.
Caller code
↓
RestApiClientUtil / ReactiveApiClientUtil (또는 자체 HTTP 클라이언트)
↓ publishEvent
ApplicationEventPublisher
↓ @EventListener (virtual threads)
ApiEventListener (api-log-core)
↓ ApiLogWriter (SPI)
├─ JpaApiLogWriter (api-log-jpa)
├─ R2dbcApiLogWriter (api-log-r2dbc)
└─ MybatisApiLogWriter (api-log-mybatis)
↓
PostgreSQL (api_log · JSONB columns)
v3.0.0부터 스타터가 4개 아티팩트로 분리됐습니다 — 백엔드 비종속 코어 1개 +
영속화 백엔드 1개. api-log-core 1개 + 백엔드 1개를 직접 골라 추가:
| 좌표 | 언제 쓰나 |
|---|---|
kr.devslab:api-log-jpa |
Servlet / JPA 앱 (v0.5.x 드롭인) |
kr.devslab:api-log-r2dbc |
WebFlux / R2DBC 앱 — JDBC 의존성 없음 |
kr.devslab:api-log-mybatis |
이미 MyBatis를 쓰고, JPA를 원치 않을 때 |
백엔드 아티팩트 각각이 api-log-core를 transitive하게 가져오므로
좌표 하나만 추가하면 됩니다.
<!-- JPA (가장 흔함 — v0.5.x 드롭인) -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-jpa</artifactId>
<version>3.0.1</version>
</dependency>
<!-- 또는 리액티브 앱에서 R2DBC -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-r2dbc</artifactId>
<version>3.0.1</version>
</dependency>
<!-- 또는 MyBatis -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-mybatis</artifactId>
<version>3.0.1</version>
</dependency>implementation("kr.devslab:api-log-jpa:3.0.1")
// 또는 "kr.devslab:api-log-r2dbc:3.0.1"
// 또는 "kr.devslab:api-log-mybatis:3.0.1"api:
log:
enabled: true # 기본값 — false면 전체 인프라 비활성화
schema:
management: builtin # 기본값 — 아래 "스키마" 참고직접 제공:
- PostgreSQL을 가리키는
DataSource ObjectMapper빈 (Spring Boot 자동 구성으로 충분)
기본 설정이면 api_log 테이블은 첫 부팅 시 자동 생성 — 별도 설정 없이 동작합니다.
api.log.schema.management로 api_log 테이블 생성 방식 선택:
builtin(기본) — 스타터가 부팅 시CREATE TABLE IF NOT EXISTS실행. 멱등적, 마이그레이션 도구 불필요.flyway— 사용자 Flyway 흐름에 등록 (flyway_schema_history로 추적).flyway-core클래스패스 필요.none— 스타터가 스키마에 손 안 댐. DDL 직접 적용.
전체 설치 가이드: api-log.devslab.kr/ko/getting-started/installation.
// GET
ApiResponse r = api.getSync("/api/users/1");
User user = api.getSyncTyped("/api/users/1", User.class);
// POST
ApiResponse r = api.postSync("/api/users", payload);
User created = api.postSyncTyped("/api/users", payload, User.class);
// 비동기
CompletableFuture<ApiResponse> f = api.postAsync("/api/users", payload);자세한 API: RestApiClientUtil 가이드.
자체 HTTP 클라이언트를 쓰면서 로깅만 활용하고 싶을 때:
@Service
@RequiredArgsConstructor
public class MyClient {
private final ApplicationEventPublisher publisher;
public void call() {
ApiRequest req = ApiRequest.builder()
.endpoint("/external/users")
.payload("{\"name\":\"John\"}")
.build();
publisher.publishEvent(new ApiCallInitiatedEvent(this, req));
try {
ApiResponse res = doHttp(req); // 자체 HTTP 호출
publisher.publishEvent(new ApiCallSuccessEvent(this, req, res));
} catch (Exception e) {
publisher.publishEvent(new ApiCallErrorEvent(this, req, e, 0, false));
}
}
}자세한 패턴 (재시도 타임라인 등): 이벤트 발행 가이드 · 재시도 처리 가이드.
| 컬럼 | 타입 | 비고 |
|---|---|---|
id |
BIGSERIAL | PK |
event_type |
VARCHAR(50) | INITIATED, SUCCESS, ERROR, RETRY_ERROR |
request_id |
VARCHAR(36) | UUID correlation id |
endpoint |
VARCHAR(255) | 대상 URL |
payload |
JSONB | 요청 본문 |
response |
JSONB | 응답 본문 |
error_message |
JSONB | {type, message, responseBody?} |
status_code |
INTEGER | HTTP 상태 (HTTP 예외에서 추출, 아니면 NULL) |
timestamp |
TIMESTAMP | 이벤트 발화 시각 |
retry_count |
INTEGER | 첫 시도는 0 |
is_retry |
BOOLEAN | 재시도 시도면 true |
전체 컬럼 + 권장 인덱스 + JSONB 형식: 스키마 레퍼런스.
-- 최근 1시간 엔드포인트별 에러율
SELECT endpoint,
COUNT(*) FILTER (WHERE event_type = 'ERROR') * 100.0 / COUNT(*) AS error_rate
FROM api_log
WHERE timestamp > NOW() - INTERVAL '1 hour'
GROUP BY endpoint
HAVING COUNT(*) > 10
ORDER BY error_rate DESC;더 많은 패턴: 로그 조회 가이드.
- Java 21+
- Spring Boot 3.5+
- PostgreSQL 15+ (JSONB)
Apache License 2.0 — LICENSE, NOTICE 참고.
Devslab 제작 · DevsLab 오픈소스 모음의 일부.