{"id":50490319,"url":"https://github.com/2nd1st/api-log","last_synced_at":"2026-06-07T07:00:47.606Z","repository":{"id":361619352,"uuid":"1255117342","full_name":"2nd1st/api-log","owner":"2nd1st","description":"Transparent LLM gateway trace recorder · sub2api / CLIProxyAPI / new-api 的 LLM 网关流量录制器","archived":false,"fork":false,"pushed_at":"2026-06-06T04:12:00.000Z","size":4594,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T06:19:39.669Z","etag":null,"topics":["anthropic","api-gateway","claude","claude-code","codex","gemini","jsonl","llmops","new-api","observability","openai-api","openai-compatible","reverse-proxy","sub2api"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/2nd1st.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T12:32:12.000Z","updated_at":"2026-06-06T04:12:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/2nd1st/api-log","commit_stats":null,"previous_names":["2nd1st/api-log"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/2nd1st/api-log","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2nd1st%2Fapi-log","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2nd1st%2Fapi-log/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2nd1st%2Fapi-log/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2nd1st%2Fapi-log/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/2nd1st","download_url":"https://codeload.github.com/2nd1st/api-log/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2nd1st%2Fapi-log/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34011812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["anthropic","api-gateway","claude","claude-code","codex","gemini","jsonl","llmops","new-api","observability","openai-api","openai-compatible","reverse-proxy","sub2api"],"created_at":"2026-06-02T02:05:19.985Z","updated_at":"2026-06-07T07:00:47.577Z","avatar_url":"https://github.com/2nd1st.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"English | [中文](README.zh.md)\n\n# api-log: LLM proxy logging and API trace recorder\n\n![api-log — tcpdump for LLM gateways](./docs/banner.png)\n\n[![CI](https://github.com/2nd1st/api-log/actions/workflows/ci.yml/badge.svg)](https://github.com/2nd1st/api-log/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/2nd1st/api-log?include_prereleases\u0026sort=semver)](https://github.com/2nd1st/api-log/releases)\n[![Go version](https://img.shields.io/github/go-mod/go-version/2nd1st/api-log)](./go.mod)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\napi-log is a transparent HTTP recording proxy for LLM gateway observability. It sits in front of sub2api, CLIProxyAPI (CPA), new-api, or any OpenAI-compatible gateway and captures OpenAI Chat, Anthropic Messages, Responses, and Gemini traffic. Each completed request/response is written as append-only JSONL with a SQLite index for local search, replay, and per-key analysis.\n\n\u003e api-log 是一个面向 LLM 网关可观测性的透明 HTTP 录制 proxy。它部署在 sub2api、CLIProxyAPI（CPA）、new-api 等 OpenAI 兼容网关前面，原样转发流量，捕获 OpenAI Chat、Anthropic Messages、Responses、Gemini 等协议。每条完成的请求/响应 trace 以 append-only JSONL 落盘，并构建 SQLite 索引，用于本地检索、重放和按 key 排查问题。\n\nThe forwarding goroutine never inspects bodies. JSON unmarshalling, SSE event splitting, and session inference happen in a finalize step after the response has been delivered to the client. Token accounting, evaluation pipelines, and any semantic interpretation are downstream of this project's scope.\n\n**Companion UI:** [api-log-viewer](https://github.com/2nd1st/api-log-viewer) — Svelte 5 SPA frontend for api-log traces; serve it from the built-in `/viewer/` route or behind Caddy/nginx next to the read API.\n\n## Status\n\nPreparing the `v0.1.0` tag. The capture path, read API, viewer (separate repo), and plugin system (Phase A observer + Phase B/C mutators) are shipped and running against live traffic. The HTTP read API contract is stable; pre-tag commits may still rebase.\n\nSee [ARCHITECTURE.md](./ARCHITECTURE.md) for the on-disk format and read API contract, and [ROADMAP.md](./ROADMAP.md) for what's queued.\n\n## vs alternatives\n\n| Tool | What it is | Why api-log is different |\n|---|---|---|\n| Helicone | Gateway / observability stack | api-log is only a transparent recorder; no routing, billing, auth, or hosted service. |\n| Langfuse | App-level LLM tracing platform | api-log captures HTTP traffic at the gateway boundary without SDK instrumentation. |\n| Phoenix | Evaluation / tracing / observability toolkit | api-log records raw gateway traces first; eval pipelines are downstream. |\n| LangSmith | LangChain tracing/eval platform | api-log is framework-agnostic and stores JSONL + SQLite locally. |\n| mitmproxy | General interactive proxy | api-log understands LLM JSON/SSE envelopes and writes structured traces. |\n\napi-log is not a gateway. It does not authenticate, route, retry, rate-limit, cache, or rewrite. Those live in the upstream gateway.\n\n## Quick start\n\n### Docker Compose\n\n```yaml\n# docker-compose.yml — added alongside your existing gateway\nservices:\n  gateway:                                  # CPA / sub2api / new-api / your stack\n    # ... existing config ...\n    expose: [\"7860\"]                        # move 7860 from \"ports\" to \"expose\"\n\n  api-log:\n    image: ghcr.io/2nd1st/api-log:latest\n    ports:\n      - \"7861:7861\"                         # proxy listener (clients connect here)\n      - \"7862:7862\"                         # read API\n    environment:\n      APILOG_PROXY_UPSTREAM: http://gateway:7860\n    volumes:\n      - ./api-log-data:/data\n```\n\n```bash\ndocker compose up -d\n```\n\nThree reference stacks live under [`deploy/`](./deploy/README.md) — `dev-stack/` (api-log + a mock LLM gateway, no real upstream needed), `demo/` (api-log in front of `sub2api`), and `bench/` (api-log alone, upstream URL via env). For a 5-minute try-it, run `deploy/dev-stack/`; that's what [`tests/integration/run.sh`](./tests/integration/run.sh) drives.\n\n### Native install\n\nFor sub2api / CLIProxyAPI / new-api operators running on a homelab box or a small VPS, install the binary directly.\n\n**Download a release binary** (no Go toolchain required) — pick the archive for your OS + arch from the [latest release](https://github.com/2nd1st/api-log/releases/latest). Linux / darwin / windows × amd64 / arm64 are pre-built (windows/arm64 deferred). Example for linux/amd64:\n\n```bash\nVERSION=v0.1.1\ncurl -L https://github.com/2nd1st/api-log/releases/download/${VERSION}/api-log_${VERSION}_linux_amd64.tar.gz | tar xz\n./api-log -version\n```\n\n**Or build from source** (requires Go 1.22+):\n\n```bash\ngo install github.com/2nd1st/api-log/cmd/api-log@latest\napi-log -version\n```\n\nThe full setup — service user, env file, data directory, systemd unit with sensible hardening defaults — lives at [`deploy/systemd/`](./deploy/systemd/). Caddy + nginx samples for putting TLS in front of the read API and the viewer SPA on the same origin live at [`deploy/reverse-proxy/`](./deploy/reverse-proxy/).\n\n### Point clients at api-log\n\nChange the client `base_url` from the gateway port (`:7860`) to the api-log proxy listener (`:7861`). No other client changes.\n\n### Verify a captured trace\n\n```bash\n# 1. Liveness — unauthenticated, k8s probe compatible\ncurl -s http://localhost:7862/healthz | jq .\n\n# 2. Send one request through the proxy (replace with a real client call)\ncurl -s http://localhost:7861/v1/messages \\\n  -H \"x-api-key: $UPSTREAM_KEY\" \\\n  -H \"anthropic-version: 2023-06-01\" \\\n  -d '{\"model\":\"claude-sonnet-4-6\",\"max_tokens\":32,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n\n# 3. Read the auto-generated admin bearer\nTOKEN=$(cat ./api-log-data/admin_token)\n\n# 4. List recent traces from the read API\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  'http://localhost:7862/api/traces?limit=5' | jq '.traces[] | {id, path, status, model}'\n```\n\nThe admin bearer is generated on first run and written to `data/admin_token`. Delete the file and restart to rotate.\n\n## Configuration\n\napi-log resolves config in three layers, highest priority last: built-in defaults, then the YAML file passed via `-config`, then `APILOG_*` environment variables. Env vars always win.\n\nThe full annotated reference — every YAML key, its default, and its env-var override — lives in [`api-log.example.yaml`](./api-log.example.yaml) at the repo root. Copy that file, uncomment only the keys you want to change, and pass it with `api-log -config /etc/api-log/api-log.yaml`. The canonical struct is [`internal/config/config.go`](./internal/config/config.go).\n\nThe env vars adopters most often override:\n\n| Env var | One-line semantics |\n|---|---|\n| `APILOG_PROXY_LISTEN` | `address:port` for the recording proxy listener clients connect to. Default `:7861`. |\n| `APILOG_PROXY_UPSTREAM` | URL of the upstream gateway api-log forwards to. Default `http://localhost:7860`. |\n| `APILOG_STORAGE_DATA_DIR` | Root for JSONL files, the SQLite index, `admin_token`, and `runtime_overrides.json`. Default `./data`. |\n| `APILOG_LOGGING_LEVEL` | slog level (`debug` \\| `info` \\| `warn` \\| `error`). Default `info`. |\n| `APILOG_API_EXPORT_BYTE_HARDCAP` | Byte ceiling on `/api/export` before `?bytes_all=1` is required. `0` (or unset) keeps the built-in 2 GiB default; positive overrides; negative disables the cap. |\n\nPlugin pattern lists (`plugins.path_filter.patterns`, `plugins.capture_filter.patterns`) and the mutator plugin instances (`text-replace`, `text-append`) have no env override — the first pair is YAML-only and the mutators live in `runtime_overrides.json`, managed via `PUT /api/config/plugins`.\n\n## How recording works\n\n### Traffic path\n\n```\nclient(s)  →  api-log  →  CPA / sub2api / new-api / any OpenAI-compatible gateway  →  upstream\n                ↓\n          data/\u003cdate\u003e/\u003ckey_hash\u003e.jsonl    (append-only, source of truth)\n          data/index.sqlite               (derived index, rebuildable)\n```\n\nThe proxy listener accepts plain HTTP. Forwarding uses `httputil.ReverseProxy` with a custom `Transport` that tees request and response bodies into per-trace temp files. When the response finishes (success, client disconnect, or upstream error), the finalize step parses the bodies into the JSONL line shape, writes one line to today's JSONL file, and inserts the indexed columns into SQLite. See [ARCHITECTURE § 7](./ARCHITECTURE.md) for the full write path.\n\n### Storage model\n\nTwo layers, in strict order of authority:\n\n1. **`data/\u003cdate\u003e/\u003ckey_hash\u003e.jsonl`** — one line per completed trace. Append-only daily file per client key. Each line carries the full HTTP transaction (request headers + body, response headers + body or `events[]` for streams, timestamps, sizes, truncation flags).\n2. **`data/index.sqlite`** — derived columns the read API needs (status, model, token counts, session linkage, `jsonl_path` + `jsonl_offset`). Deletable; rebuilt from layer 1 in seconds. WAL-mode, conn pool of 10 (v0.1.1).\n\nWhen the JSONL file and SQLite disagree, JSONL wins.\n\n#### Retention (v0.1.1)\n\nOff by default. Toggle via `PUT /api/config/retention` (persisted to `runtime_overrides.json`, survives restart):\n\n```bash\ncurl -X PUT -H \"Authorization: Bearer $ADMIN_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"max_bytes\":10000000000,\"max_age_days\":30}' \\\n  http://localhost:7862/api/config/retention\n```\n\nBoth knobs `0` keeps the engine running (inventory + `/healthz.storage` reporting) but never deletes. See [docs/retention.md](./docs/retention.md) for thresholds, eviction order, and the lease semantics that keep retention from racing with the writer.\n\n### JSONL trace shape\n\n```json\n{\n  \"id\": \"01HX7K8MS...\",\n  \"ts_start\": \"2026-05-27T10:23:45.123Z\",\n  \"ts_end\":   \"2026-05-27T10:23:46.357Z\",\n  \"client\":   \"172.17.0.5:54321\",\n  \"method\":   \"POST\",\n  \"path\":     \"/v1/messages\",\n  \"upstream\": \"http://gateway:7860\",\n  \"status\":   200,\n  \"req\": {\n    \"headers\": {\"x-api-key\": \"sk-***\", \"anthropic-version\": \"2023-06-01\"},\n    \"body\":    {\"model\": \"claude-sonnet-4-6\", \"messages\": [...], \"stream\": true}\n  },\n  \"resp\": {\n    \"headers\": {\"content-type\": \"text/event-stream\", \"x-request-id\": \"...\"},\n    \"events\":  [\n      {\"event\": \"message_start\",       \"data\": {\"message\": {\"id\": \"msg_...\", ...}}, \"t_delta_ms\":  12},\n      {\"event\": \"content_block_delta\", \"data\": {\"delta\": {\"text\": \"Hello\"}, ...},   \"t_delta_ms\": 234},\n      {\"event\": \"message_delta\",       \"data\": {\"usage\": {\"output_tokens\": 8}},     \"t_delta_ms\": 511},\n      {\"event\": \"message_stop\",        \"data\": {},                                  \"t_delta_ms\": 514}\n    ],\n    \"stream_done\": true\n  },\n  \"disconnected\":   false,\n  \"truncated_req\":  false,\n  \"truncated_resp\": false\n}\n```\n\nHeader values shown above are redacted for documentation; the on-disk JSONL contains the raw bytes the client sent. See [Security](#security) below.\n\nThe full field reference lives in [ARCHITECTURE § 3](./ARCHITECTURE.md).\n\n## Protocol coverage\n\napi-log forwards every HTTP request byte-faithfully. The finalize step parses bodies for a known set of LLM API shapes; unrecognized paths still record headers + raw body but skip the structured fields.\n\n- **OpenAI Chat Completions** (`/v1/chat/completions`) — request `messages[]`, response `choices[]`, both streamed (`data: {...}\\n\\n` chunks) and non-streamed.\n- **OpenAI Responses** (`/v1/responses`) — request `input[]`, response `output[]`, including SSE events such as `response.output_item.added` for tool-call extraction.\n- **Anthropic Messages** (`/v1/messages`) — request `messages[]` + `system`, response `content[]`, SSE events `message_start` / `content_block_delta` / `message_delta` / `message_stop`.\n- **SSE streams** — each `event:` / `data:` pair is split into one entry in `resp.events[]` with `t_delta_ms` recorded against the response start. Non-SSE responses land as parsed JSON in `resp.body`.\n- **After-mutation capture for streams** — when plugins mutate a streaming response (Phase B/C), api-log records the bytes the client actually received, not the upstream pre-mutation stream.\n\nTool calls, reasoning blocks, and any other content these protocols carry live verbatim inside `req.body` and `resp.body` / `resp.events[]`. They are not promoted to top-level fields and api-log does not interpret them.\n\n## Query examples\n\nThe examples below are reference snippets, not benchmarked workloads. Run them against your own data.\n\n### jq over JSONL\n\nTool-call frequency across captured Responses-API traces:\n\n```bash\nzcat data/2026-*/*.jsonl{,.gz} 2\u003e/dev/null \\\n  | jq -r '.resp.events[]? | select(.event==\"response.output_item.added\")\n           | .data.item | select(.type==\"function_call\") | .name' \\\n  | sort | uniq -c | sort -rn\n```\n\nStreams that were cut short:\n\n```bash\njq 'select(.disconnected==true or .resp.stream_done==false)\n    | {id, path, status, ts_start}' \\\n  data/2026-05-27/*.jsonl\n```\n\n### sqlite3 over the index\n\nRecent failing traces by model and path:\n\n```bash\nsqlite3 data/index.sqlite \\\n  \"SELECT ts_start, model, path, status FROM traces\n   WHERE status \u003e= 400\n   ORDER BY ts_start DESC LIMIT 20\"\n```\n\nAll traces in one session, in order:\n\n```bash\nsqlite3 data/index.sqlite \\\n  \"SELECT id, jsonl_path, jsonl_offset FROM traces\n   WHERE session_root_id='01HX7K...' ORDER BY ts_start\"\n```\n\nThe `jsonl_path` + `jsonl_offset` pair lets a consumer seek directly to the line in the JSONL file. AI agents doing batch analysis can bypass the read API and read JSONL files off disk.\n\n### Replay a recorded SSE stream\n\n```bash\ncurl -N -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:7862/api/traces/01HX7K8MS.../replay?speed=2\"\n```\n\n`/api/traces/:id/replay` re-emits the recorded SSE frames at original per-chunk pacing (or `speed` × faster). `speed=2` halves the delays; `nodelay=1` dumps every event back-to-back. The replay is to the API caller; it never re-contacts the upstream LLM. See [ARCHITECTURE § 6.4](./ARCHITECTURE.md) for the full semantics, including how reparsed traces handle missing `t_delta_ms`.\n\n## Read API\n\nThe read API listens on a separate port (`:7862` by default) from the proxy. Thirteen routes total; the full surface lives in [ARCHITECTURE § 6](./ARCHITECTURE.md). Highlights:\n\n- `GET /healthz` — unauthenticated; exposes in-memory drop / overflow counters so operators can detect capture degradation without grepping logs.\n- `GET /api/traces` — list, SQLite-backed, supports `since` / `until` / `status` (exact or `2xx` / `4xx` / `5xx` bucket) / `model` / `path` (trailing `*` for prefix) / `key_hash` / `session_root_id` / `project` / `limit` / cursor pagination.\n- `GET /api/traces/:id` — detail; returns `{row, trace}` so callers get both the SQLite row and the parsed JSONL line in one round trip.\n- `GET /api/traces/:id/replay` — pacing-preserving SSE replay.\n- `GET /api/sessions` — session summaries grouped by `session_root_id`. Row-level aggregation only; not a tree walk.\n- `GET /api/export` — streams a zip of matching JSONL lines + bundled `agent/CLAUDE.md` for offline / AI-assisted analysis.\n- `GET/PUT /api/config/plugins` (+ `PUT /api/config/plugins/:id`, `DELETE /api/config/plugins`, `GET /api/plugins/types`) — hot-reload of `text-replace` / `text-append` / `path-filter` plugins. YAML remains the declarative truth; runtime overrides persist to `data/runtime_overrides.json`. Off by default.\n\nAll read endpoints except `/healthz` require `Authorization: Bearer \u003cdata/admin_token\u003e` and respond with `Cache-Control: no-store`.\n\napi-log ships **no embedded HTML viewer**. `GET /` returns a JSON pointer to the separate [api-log-viewer](https://github.com/2nd1st/api-log-viewer) project; the binary contains zero HTML.\n\n## Bundled viewer\n\napi-log ships hosted viewer at `/viewer/` by default. On first request, the backend fetches `dist.zip` from a pinned release of [`api-log-viewer`](https://github.com/2nd1st/api-log-viewer), verifies the asset's SHA-256 against a constant baked into the backend binary, and extracts the bundle to a cache under `data/viewer-cache/`. Subsequent requests serve from the cache.\n\nA SHA-256 mismatch is fatal to the route — `/viewer/` returns 503 and the binary logs the mismatch. The backend never serves an unverified asset.\n\nOverride knobs (env or YAML):\n\n| Knob | Default | Effect |\n|---|---|---|\n| `APILOG_VIEWER_ENABLED` | true | Set false to skip hosting; `/viewer/` returns 503 `disabled`. |\n| `APILOG_VIEWER_REPO` | `2nd1st/api-log-viewer` | Point at any GitHub repo. |\n| `APILOG_VIEWER_VERSION` | pinned to the backend release | Tag to fetch. |\n| `APILOG_VIEWER_SHA256` | pinned to the backend release | Must be supplied when overriding REPO or VERSION; mismatch returns 503. |\n| `APILOG_VIEWER_LOCAL_PATH` | (unset) | Absolute path to a `dist/` directory. Skips fetch + verify entirely; useful for offline / air-gapped deployments and for local viewer development. |\n| `APILOG_VIEWER_CACHE_DIR` | `\u003cdata_dir\u003e/viewer-cache` | Cache root. |\n| `APILOG_VIEWER_PUBLIC_PATH` | `/viewer` | URL prefix. |\n\nThe backend never auto-updates the cached viewer — fetch happens once per (repo, version, sha) tuple. To roll forward, bump the backend release (which bumps the pinned constants) or override `APILOG_VIEWER_VERSION` + `APILOG_VIEWER_SHA256` explicitly.\n\n## Security\n\n**Bearer tokens land on disk unredacted.** The JSONL files contain the raw `Authorization` / `x-api-key` headers exactly as the client sent them. api-log does not redact anything from the capture path. Treat the `data/` directory the way you would treat `~/.ssh/` or a file holding production API keys:\n\n- Mount `data/` to a path with restrictive filesystem permissions (`chmod 700` or tighter).\n- Do not expose the proxy listener (`:7861`) or the read API listener (`:7862`) to untrusted networks without a reverse proxy enforcing transport security and access control.\n- The auto-generated admin bearer at `data/admin_token` is the only credential gating the read API. Rotate it by deleting the file and restarting.\n- Plain HTTP between containers on the same host is the primary supported topology. If you need TLS or cross-host routing, terminate it with whatever reverse proxy you already run; api-log itself listens HTTP only.\n\nRedaction is a deliberate non-goal of the capture path. If you need redacted traces, run a sidecar over the JSONL files; the on-disk format is documented and stable.\n\nSee [SECURITY.md](./SECURITY.md) for the threat model and disclosure process.\n\n## Development\n\n```bash\ngit clone https://github.com/2nd1st/api-log.git\ncd api-log\n\n# unit + integration tests (race detector on)\ngo test ./...\n\n# lint (golangci-lint v2)\ngolangci-lint run\n\n# build\ngo build -o bin/api-log ./cmd/api-log\n\n# the dev-stack integration harness\n./tests/integration/run.sh\n```\n\nThe project ships 23 Go packages, race-clean tests, and CI lint via `golangci-lint v2`. CI runs on every push and PR; see [`.github/workflows/ci.yml`](./.github/workflows/ci.yml).\n\n## Roadmap\n\n- [x] **v0** — capture path (parse + JSONL write + SQLite mirror), session inference, minimal read API.\n- [x] **v0.1 viewer** — [api-log-viewer](https://github.com/2nd1st/api-log-viewer) — multi-instance aggregation, session-tree visualization, tool-call rendering, SSE replay.\n- [x] **v0.1 plugins** — `text-replace` / `text-append` / `path-filter` with hot-reload via `PUT /api/config/plugins`. Off by default.\n- [ ] **v0.2** — optional per-gateway **bridge adapters** (separate projects) — join external data (CPA's Redis usage queue, new-api's MySQL log table) into api-log traces by `key_hash`. The core proxy stays gateway-agnostic.\n\nSee [ROADMAP.md](./ROADMAP.md) for the full list including the `v0.1.0-deferred` section.\n\n## Acknowledgements\n\n### Design influence\n\n- **tcpdump / pcap** — append-only capture with deferred interpretation\n- **CLIProxyAPI (CPA)** — single-binary, single-config aesthetic\n- **Claude Code / Codex CLI** — local JSONL session files as a usable format\n- **Langfuse** — the LLM-observability surface, against which we deliberately differ on the capture-vs-instrument axis\n\n### Live-traffic iteration partner\n\n- **sub2api** — primary upstream gateway used to validate the capture path against live traffic; the path-filter pattern set and the client-identification taxonomy were tuned against its real-traffic shapes.\n\n### Development assistance\n\nThis codebase and its documentation were developed with **Claude Opus 4.8** (Anthropic) as the primary pair-programmer for both code and prose, and **GPT-5.5** via Codex CLI as a research and review assistant — adversarial pre-release review, README structural analysis against reference OSS projects, and fact-check cross-checks. The choices on what to keep, cut, or amend are the human author's; AI assistance is named here for transparency, not as authorship.\n\n## License\n\n[MIT](./LICENSE) — © 2026 Leo Yun.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2nd1st%2Fapi-log","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F2nd1st%2Fapi-log","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2nd1st%2Fapi-log/lists"}