Skip to content

Releases: 2nd1st/api-log

v0.1.3

06 Jun 04:14

Choose a tag to compare

v0.1.3 — default retention 30d/5GB + gzip level 6 + api-log package s…

v0.1.2

02 Jun 02:53

Choose a tag to compare

Capture-side path drop plugin + /api/export byte-cap + curl client kind.

Highlights:

  • New capture_filter plugin drops paths BEFORE writing (vs the existing observer-only path_filter).
  • /api/export byte-cap (default 2 GiB, ?bytes_all=1 bypass; independent of the row cap).
  • Byte-cap wired to APILOG_API_EXPORT_BYTE_HARDCAP env / api.export_byte_hardcap YAML.
  • curl UA classified as its own kind.

Added

  • /api/export byte-cap pre-flight (commit ff0c42f):
    complementary safety net to the v0.1.1 row cap. Default 2 GiB;
    ?bytes_all=1 bypass is independent of ?all=1 (each cap names
    what it lets through). Measured in source JSONL bytes (sum of
    os.Stat file sizes for matched groups; one syscall per group, not
    per row). Over-cap returns 413 with limit:"bytes" discriminator
    before any zip bytes hit the wire. Audit-driven: sub2api's 9 GB /
    ~6.9 GB+ zip didn't trip the 50000-row cap because media-heavy
    rows are bytes-per-row dominant, not row-dominant.
  • capture_filter plugin (commit d64863c): pre-write path
    drop, sister to the existing observer-class path_filter. Where
    path_filter drops at finalize (after bytes ran through the
    capture sinks), capture_filter is checked in the proxy handler
    BEFORE startTrace — a matched path skips JSONL + SQLite + media
    • body tee entirely; the request still forwards upstream unchanged.
      Audit-driven (v0.1.1 deploy workflow showed sub2api stores 86%
      control-plane polling that's not LLM-observability material).
      Opt-in via plugins.capture_filter.patterns; existing
      plugins.path_filter deployments are not silently re-interpreted.
      Pattern syntax mirrors path_filter. Counter
      traces_dropped_capture_filter surfaces drop activity on
      /healthz.counters. Live-verified on sub2gpt with
      /api/v1/auth/* + /api/v1/subscriptions/* patterns.
  • curl UA in client taxonomy (commit f60c66d): rule 11 in
    parser/client.go, placed after go-http-client. Generalises
    smoke-test + healthprobe traffic across every adopter. Justified
    by a Workflow audit (we0wvgjhv) that sampled 80 of 197 null
    client_kind rows on sub2gpt and found 7 clean curl/8.7.1 UAs.

Changed

  • pathfilter.Pattern + pathfilter.Compile + pathfilter.MatchAny
    are now exported so sibling plugins (initially capturefilter)
    reuse one validated parse + match implementation. Behavior of
    pathfilter.Plugin.Init is unchanged.

Fixed

Removed

v0.1.1

02 Jun 01:01

Choose a tag to compare

Storage retention coordinator + streaming /api/export + Gitea-mirror-friendly viewer fetch.

Highlights:

  • Storage coordinator with lease arbitration, retention engine, /api/config/retention GET/PUT.
  • Streaming /api/export (50000-row pre-flight, Deflate-1, lease-protected groups).
  • Viewer pluggable releases endpoint (Gitea / Forgejo).
  • Writer idle-close releases bucket leases for retention.

Added

  • B1 / B2 — storage coordinator + lease arbitration (commit
    ce31ac2): new internal/storage/ package owns file identity
    (FileID), refcount leases, on-disk inventory, status, and
    eviction. Writer acquires a per-(date, keyhash) bucket lease
    BEFORE opening the JSONL — eviction's deleteIfIdle refuses while
    any lease is held. Foundation for runtime-toggleable retention
    without TOCTOU. Adds idempotent idx_jsonl_path index; switches
    SQLite DSN to embed journal_mode=WAL + busy_timeout(5000) +
    foreign_keys(on) so every conn the pool hands out has the right
    pragmas (was only the first); bumps MaxOpenConns 8 → 10 to
    absorb the storage monitor's reconcile sweep. media.Extract(t, bucket) now takes the writer-chosen bucket FileID so media
    co-locates with its JSONL across UTC midnight rotation.
  • B3 — streaming /api/export with 413 pre-flight (commit
    e73f5c2): two-phase pipeline — Phase 1 borrows a single SQLite
    conn via the new StreamMatching cursor and walks rows building
    per-file groups + slim media refs; Phase 2 builds the zip after
    the cursor + conn release, with RegisterCompressor swapping
    Deflate level 6 → level 1 (~3× wall-clock for ~5% size on 100k+
    row exports). Pre-flight CountMatching gates oversized exports
    at a 50000-row default cap and returns 413 with a JSON pointer to
    the bypass flag BEFORE any zip bytes hit the wire; ?all=1
    disables. New project= filter wired through; r.Context()
    threads through the cursor + zip loop for clean mid-export cancel.
  • B4 — /healthz.storage + /api/config/retention GET+PUT
    (commit 8b1287c): operator-facing surface for the storage
    coordinator. PUT validates against the same rules storage.New
    enforces, applies in-memory FIRST via coord.UpdateConfig, then
    persists to runtime_overrides.json so the next process start
    picks it up without env / yaml plumbing. Both knobs zero ==
    disable retention without restart. /healthz gains a top-level
    storage key carrying DataDirBytes / MaxBytes / MaxAgeDays / UsagePct / State / EngineRunning / EvictionCapHit plus
    conditional LastEvictionTs / LastEvictedBytes.
  • B5 — writer idle-close (commit 8a95408): after N min
    (default 10m) without an append, the writer releases the bucket
    lease and closes the OS handle so retention's byte-cap can touch
    today's quiet buckets and long-tail keys don't pin a process-
    lifetime fd each. Idle-close does NOT gzip — that's strictly a
    date-cross event. SetIdleTimeout is the public knob; tests
    pass tiny values without exposing a constructor param.

Changed

  • exporter.WriteZip signature: (ctx, w, store, dataDir, filters, coord). Removed limit int — pre-flight CountMatching is the
    gate now; the cursor walks unbounded. Existing test callsites
    pass context.Background().
  • writer.New signature gains coord *storage.Coordinator as the
    8th argument before clock. nil keeps the v0.1.0 behavior
    (no lease arbitration); production callers wire a non-nil coord.
  • media.Extract(t)media.Extract(t, bucket storage.FileID).
    Caller passes the writer-chosen bucket; fixes a UTC-midnight
    co-location bug where a trace's TsStart could resolve a different
    bucket than the writer just wrote the JSONL to.
  • internal/api.Deps gains StorageCoord *storage.Coordinator.
    Nil-safe — /healthz omits the storage block, /api/config/ retention returns 503, and exporter.WriteZip falls back to
    lease-less reads.
  • counters.Counters gains AddEvictedTraces / AddEvictedBytes
    • matching Snapshot.EvictedTraces / EvictedBytes.
  • UpdateConfig synthesizes a baseline Status when called before
    the first monitor tick so PUT-then-GET sees the new thresholds
    instead of pending. EngineRunning stays false until the
    monitor goroutine actually starts.

Added (late-cycle)

  • Pluggable viewer releases endpoint (commit 20b2275): new
    APILOG_VIEWER_RELEASES_API_BASE + APILOG_VIEWER_RELEASES_AUTH_TOKEN
    config / env knobs route the dist.zip fetch at non-GitHub stores
    (Gitea, Forgejo, GHE). Useful for staging viewer changes against an
    internal artifact mirror before cutting a public release; tested live
    on sub2gpt pulling from gitea.homelab.lan (Gitea release shape is
    GitHub-compatible). Default empty keeps v0.1.0 behavior.

Fixed

  • exporter.summarizeFilters (internal/exporter/exporter.go) now
    emits a line for the Project filter in the in-zip README.md. The
    filter was wired through SQLite correctly since v0.1.1's streaming
    rewrite; only the human-readable summary line was missing. Caught by
    the v0.1.1 pre-tag deploy audit (commit ae5a97d).

v0.1.0

01 Jun 04:23

Choose a tag to compare

api-log v0.1.0 is the first public backend release: a transparent LLM gateway trace recorder for sub2api, CLIProxyAPI (CPA), new-api, and other OpenAI-compatible gateway stacks.

Quick start

docker pull ghcr.io/2nd1st/api-log:0.1.0

Minimal Docker Compose shape:

services:
  gateway:
    # sub2api / CPA / new-api / your existing gateway
    expose: ["7860"]

  api-log:
    image: ghcr.io/2nd1st/api-log:0.1.0
    ports:
      - "7861:7861"
      - "7862:7862"
    environment:
      APILOG_PROXY_UPSTREAM: http://gateway:7860
    volumes:
      - ./api-log-data:/data

Point clients at http://localhost:7861 instead of the gateway port. Read captured traces from the authenticated read API on http://localhost:7862.

Supported protocol surfaces

  • OpenAI Chat Completions: /v1/chat/completions
  • Anthropic Messages: /v1/messages
  • OpenAI Responses: /v1/responses
  • OpenAI Image Generations: /v1/images/generations
  • Gemini generate/stream surfaces used by compatible gateways

Streaming responses are captured as SSE event arrays where the protocol exposes SSE.

Security notice

api-log records raw HTTP traffic. `Authorization` and `x-api-key` headers are written to JSONL exactly as clients sent them. Treat the `data/` directory like production API-key material: run api-log only on trusted networks, restrict filesystem access, and apply disk-encryption / backup policy accordingly.

The proxy listener does not authenticate clients. Network access control belongs at the operator layer.

Viewer

The companion UI is released separately:

https://github.com/2nd1st/api-log-viewer/releases/tag/v0.1.0

This backend can serve the pinned viewer bundle at `/viewer/` by default. The backend fetches the pinned dist.zip, verifies its SHA-256, extracts it into `data/viewer-cache/`, and serves it without embedding HTML into the binary.

Out of scope

Token accounting, redaction, billing, and eval pipelines are not built into api-log — they run as downstream JSONL processors. Replay returns recorded responses to read-API callers only; it never re-contacts the upstream LLM gateway. Hosted viewer updates are pinned by backend release; there is no auto-update path by design.

v0.1.x direction

Protocol edge cases, read-API filters, viewer pin updates, and adopter docs for sub2api / CPA / new-api deployments.