{"id":50100674,"url":"https://github.com/devslab-kr/api-log","last_synced_at":"2026-05-23T07:14:40.051Z","repository":{"id":315778247,"uuid":"1060796291","full_name":"devslab-kr/api-log","owner":"devslab-kr","description":"Event-driven API call logging for Spring Boot — async event pipeline with PostgreSQL JSONB storage.","archived":false,"fork":false,"pushed_at":"2026-05-17T17:28:23.000Z","size":837,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-17T17:40:50.474Z","etag":null,"topics":["api-logging","event-driven","java","jsonb","logging","postgresql","spring-boot"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devslab-kr.png","metadata":{"files":{"readme":"README.ko.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.ko.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-20T16:00:30.000Z","updated_at":"2026-05-17T17:28:08.000Z","dependencies_parsed_at":"2025-09-20T18:18:48.691Z","dependency_job_id":"ff0dc7fc-074c-4f5f-a02a-10b777666656","html_url":"https://github.com/devslab-kr/api-log","commit_stats":null,"previous_names":["jlc488/api_log","devslab-kr/api-log"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/devslab-kr/api-log","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fapi-log","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fapi-log/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fapi-log/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fapi-log/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devslab-kr","download_url":"https://codeload.github.com/devslab-kr/api-log/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fapi-log/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33149519,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-logging","event-driven","java","jsonb","logging","postgresql","spring-boot"],"created_at":"2026-05-23T07:14:38.116Z","updated_at":"2026-05-23T07:14:40.046Z","avatar_url":"https://github.com/devslab-kr.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# api-log-spring-boot-starter\n\n[English](README.md) · **한국어**\n\n\u003e Spring Boot용 이벤트 드리븐 API 호출 로깅. 비동기 이벤트 파이프라인 + PostgreSQL JSONB. 요청 경로를 막지 않고 외부 API 호출을 모두 기록합니다.\n\n[![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/api-log-core.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/kr.devslab/api-log-core)\n[![CI](https://github.com/devslab-kr/api-log/actions/workflows/ci.yml/badge.svg)](https://github.com/devslab-kr/api-log/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/devslab-kr/api-log/branch/master/graph/badge.svg)](https://codecov.io/gh/devslab-kr/api-log)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![Java](https://img.shields.io/badge/Java-21+-orange.svg)](https://openjdk.org/projects/jdk/21/)\n[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5+-green.svg)](https://spring.io/projects/spring-boot)\n\n📖 **[문서 → api-log.devslab.kr](https://api-log.devslab.kr/ko/)**\n\n## 무엇을 하나\n\n서비스의 모든 외부 HTTP 호출 — 요청, 응답, 에러, 재시도 — 을 PostgreSQL에 기록합니다. 논블로킹 이벤트 파이프라인으로 동작하므로 호출자는 로그 쓰기를 기다리지 않습니다. 번들된 `RestApiClientUtil`로 보낸 호출이든, 직접 발행하는 이벤트든, JSONB로 저장됩니다.\n\n## 한눈에 보기\n\n```java\n@Service\npublic class UserService {\n\n    private final RestApiClientUtil api;\n\n    public UserService(RestApiClientUtil api) {\n        this.api = api;\n    }\n\n    public User createUser(User newUser) {\n        // HTTP 호출은 동기적이고; 로깅 이벤트는 백그라운드에서 발화.\n        return api.postSyncTyped(\"/api/users\", newUser, User.class);\n    }\n}\n```\n\n한 호출이 `api_log`에 한 행 이상 생성:\n\n- **INITIATED** — 요청 발사\n- **SUCCESS** / **ERROR** — 종료 결과 (상태 코드 + 페이로드)\n- **RETRY_ERROR** — 재시도 실패 시 (사용자가 직접 발행)\n\n본문은 JSONB로 저장되어 `-\u003e`, `-\u003e\u003e`, GIN 인덱스로 자유롭게 조회 가능.\n\n## 핵심 가치\n\n- **논블로킹** — 로그 쓰기는 별도 스레드. HTTP 호출 경로는 절대 막히지 않음.\n- **PostgreSQL JSONB** — 요청·응답·에러 본문이 조회 가능한 JSON\n- **재시도 인식 스키마** — `RETRY_ERROR` 이벤트 + `retry_count` / `is_retry` 컬럼. 리스너도 일시적 DB 실패에 대해 로그 쓰기를 3회 재시도.\n- **Virtual Threads 지원** — Java 21+ 비동기 설계\n- **Drop-in 스타터** — 자동 구성으로 모든 빈 등록. 직접 빈 정의하면 오버라이드.\n\n## 아키텍처\n\n```\nCaller code\n   ↓\nRestApiClientUtil / ReactiveApiClientUtil  (또는 자체 HTTP 클라이언트)\n   ↓ publishEvent\nApplicationEventPublisher\n   ↓ @EventListener (virtual threads)\nApiEventListener  (api-log-core)\n   ↓ ApiLogWriter (SPI)\n   ├─ JpaApiLogWriter      (api-log-jpa)\n   ├─ R2dbcApiLogWriter    (api-log-r2dbc)\n   └─ MybatisApiLogWriter  (api-log-mybatis)\n        ↓\n   PostgreSQL  (api_log · JSONB columns)\n```\n\n## 설치\n\nv0.6.0부터 스타터가 4개 아티팩트로 분리됐습니다 — 백엔드 비종속 코어 1개 +\n영속화 백엔드 1개. **`api-log-core` 1개 + 백엔드 1개**를 직접 골라 추가:\n\n| 좌표 | 언제 쓰나 |\n| --- | --- |\n| `kr.devslab:api-log-jpa` | Servlet / JPA 앱 (v0.5.x 드롭인) |\n| `kr.devslab:api-log-r2dbc` | WebFlux / R2DBC 앱 — JDBC 의존성 없음 |\n| `kr.devslab:api-log-mybatis` | 이미 MyBatis를 쓰고, JPA를 원치 않을 때 |\n\n백엔드 아티팩트 각각이 `api-log-core`를 transitive하게 가져오므로\n좌표 하나만 추가하면 됩니다.\n\n### Maven\n\n```xml\n\u003c!-- JPA (가장 흔함 — v0.5.x 드롭인) --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ekr.devslab\u003c/groupId\u003e\n    \u003cartifactId\u003eapi-log-jpa\u003c/artifactId\u003e\n    \u003cversion\u003e0.6.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- 또는 리액티브 앱에서 R2DBC --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ekr.devslab\u003c/groupId\u003e\n    \u003cartifactId\u003eapi-log-r2dbc\u003c/artifactId\u003e\n    \u003cversion\u003e0.6.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- 또는 MyBatis --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ekr.devslab\u003c/groupId\u003e\n    \u003cartifactId\u003eapi-log-mybatis\u003c/artifactId\u003e\n    \u003cversion\u003e0.6.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle\n\n```kotlin\nimplementation(\"kr.devslab:api-log-jpa:0.6.0\")\n// 또는 \"kr.devslab:api-log-r2dbc:0.6.0\"\n// 또는 \"kr.devslab:api-log-mybatis:0.6.0\"\n```\n\n## 설정\n\n```yaml\napi:\n  log:\n    enabled: true              # 기본값 — false면 전체 인프라 비활성화\n    schema:\n      management: builtin      # 기본값 — 아래 \"스키마\" 참고\n```\n\n직접 제공:\n\n- PostgreSQL을 가리키는 `DataSource`\n- `ObjectMapper` 빈 (Spring Boot 자동 구성으로 충분)\n\n기본 설정이면 `api_log` 테이블은 첫 부팅 시 자동 생성 — 별도 설정 없이 동작합니다.\n\n### 스키마 관리\n\n`api.log.schema.management`로 `api_log` 테이블 생성 방식 선택:\n\n- **`builtin`** (기본) — 스타터가 부팅 시 `CREATE TABLE IF NOT EXISTS` 실행. 멱등적, 마이그레이션 도구 불필요.\n- **`flyway`** — 사용자 Flyway 흐름에 등록 (`flyway_schema_history`로 추적). `flyway-core` 클래스패스 필요.\n- **`none`** — 스타터가 스키마에 손 안 댐. DDL 직접 적용.\n\n전체 설치 가이드: [api-log.devslab.kr/ko/getting-started/installation](https://api-log.devslab.kr/ko/getting-started/installation/).\n\n## `RestApiClientUtil` 사용\n\n```java\n// GET\nApiResponse r = api.getSync(\"/api/users/1\");\nUser user    = api.getSyncTyped(\"/api/users/1\", User.class);\n\n// POST\nApiResponse r = api.postSync(\"/api/users\", payload);\nUser created = api.postSyncTyped(\"/api/users\", payload, User.class);\n\n// 비동기\nCompletableFuture\u003cApiResponse\u003e f = api.postAsync(\"/api/users\", payload);\n```\n\n자세한 API: [RestApiClientUtil 가이드](https://api-log.devslab.kr/ko/guides/using-restapiclient/).\n\n## 이벤트 직접 발행\n\n자체 HTTP 클라이언트를 쓰면서 로깅만 활용하고 싶을 때:\n\n```java\n@Service\n@RequiredArgsConstructor\npublic class MyClient {\n\n    private final ApplicationEventPublisher publisher;\n\n    public void call() {\n        ApiRequest req = ApiRequest.builder()\n                .endpoint(\"/external/users\")\n                .payload(\"{\\\"name\\\":\\\"John\\\"}\")\n                .build();\n\n        publisher.publishEvent(new ApiCallInitiatedEvent(this, req));\n        try {\n            ApiResponse res = doHttp(req);              // 자체 HTTP 호출\n            publisher.publishEvent(new ApiCallSuccessEvent(this, req, res));\n        } catch (Exception e) {\n            publisher.publishEvent(new ApiCallErrorEvent(this, req, e, 0, false));\n        }\n    }\n}\n```\n\n자세한 패턴 (재시도 타임라인 등): [이벤트 발행 가이드](https://api-log.devslab.kr/ko/guides/publishing-events/) · [재시도 처리 가이드](https://api-log.devslab.kr/ko/guides/retry-handling/).\n\n## 스키마\n\n| 컬럼 | 타입 | 비고 |\n|---|---|---|\n| `id` | BIGSERIAL | PK |\n| `event_type` | VARCHAR(50) | `INITIATED`, `SUCCESS`, `ERROR`, `RETRY_ERROR` |\n| `request_id` | VARCHAR(36) | UUID correlation id |\n| `endpoint` | VARCHAR(255) | 대상 URL |\n| `payload` | JSONB | 요청 본문 |\n| `response` | JSONB | 응답 본문 |\n| `error_message` | JSONB | `{type, message, responseBody?}` |\n| `status_code` | INTEGER | HTTP 상태 (HTTP 예외에서 추출, 아니면 NULL) |\n| `timestamp` | TIMESTAMP | 이벤트 발화 시각 |\n| `retry_count` | INTEGER | 첫 시도는 `0` |\n| `is_retry` | BOOLEAN | 재시도 시도면 `true` |\n\n전체 컬럼 + 권장 인덱스 + JSONB 형식: [스키마 레퍼런스](https://api-log.devslab.kr/ko/reference/schema/).\n\n## 예시 쿼리\n\n```sql\n-- 최근 1시간 엔드포인트별 에러율\nSELECT endpoint,\n       COUNT(*) FILTER (WHERE event_type = 'ERROR') * 100.0 / COUNT(*) AS error_rate\nFROM api_log\nWHERE timestamp \u003e NOW() - INTERVAL '1 hour'\nGROUP BY endpoint\nHAVING COUNT(*) \u003e 10\nORDER BY error_rate DESC;\n```\n\n더 많은 패턴: [로그 조회 가이드](https://api-log.devslab.kr/ko/guides/querying-logs/).\n\n## 요구사항\n\n- Java 21+\n- Spring Boot 3.5+\n- PostgreSQL 15+ (JSONB)\n\n## 라이선스\n\nApache License 2.0 — [LICENSE](LICENSE), [NOTICE](NOTICE) 참고.\n\n---\n\n[Devslab](https://devslab.kr) 제작 · [DevsLab 오픈소스 모음](https://github.com/devslab-kr)의 일부.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevslab-kr%2Fapi-log","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevslab-kr%2Fapi-log","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevslab-kr%2Fapi-log/lists"}