Sign inSign up

ygolovnia/auditgate

By ygolovnia

•Updated 3 months ago

HTTP reverse proxy for API audit logging. Provide full request/response/timing observability.

Image
Networking
API management
Developer tools
1

1.1K

ygolovnia/auditgate repository overview

⁠Auditgate

A lightweight HTTP reverse proxy for API audit logging. Sits between your clients and backend, recording every request and response with precise timing — even when the client times out. Log everything, or only when errors occur. Traffic is never modified.

Built for teams that need full observability over third-party or internal APIs: who called what, when, what the backend returned, and how long each layer took.


⁠Features

  • Structured JSON logging — every request/response pair logged with zerolog, including body, headers, status codes, and timing
  • Three route modes — logged_endpoints for full logging, error_endpoints for logging only on selected HTTP error codes, proxied_endpoints for silent passthrough (health checks, static assets)
  • Accurate latency breakdown — separates proxy overhead (dispatch_ms), pure backend time (backend_duration_ms), and total wall time (total_duration_ms)
  • Client disconnect handling — if a client times out, the proxy still waits for the backend, logs the full response, and marks the event as disconnected
  • Health check endpoint — GET /gate-health reports auditgate status and backend reachability
  • TCP-level IP filtering — allowlist CIDRs; blocked connections are dropped before HTTP parsing
  • Forbidden headers — strip sensitive headers (e.g. Authorization, Cookie) from logs without affecting proxied traffic
  • Log rotation — via lumberjack: size limit, backup count, age, optional gzip compression
  • Graceful shutdown — drains in-flight requests within a configurable timeout
  • Buffer pooling — sync.Pool for request/response body buffers to minimise allocations under load

⁠Quick start

  1. Create config.json (see Configuration below)
  2. Run:
services:
  auditgate:
    image: ygolovnia/auditgate:latest
    ports:
      - "8080:8080"
    volumes:
      - ./config.json:/app/config.json:ro
    environment:
      - TZ=Europe/Kyiv
      - LOG_TO_STDOUT=true
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "10"
    restart: unless-stopped
  1. Test it — curl http://localhost:8080/posts/1
  2. Check health — curl http://localhost:8080/gate-health

⁠Configuration

The config follows the natural user story: where to proxy → what to log → how to log it → where to store → operations.

{
  "target_url": "https://jsonplaceholder.typicode.com",

  "logged_endpoints": [
    "GET /posts",
    "POST /posts"
  ],
  "error_endpoints": [
    "GET /posts/*",
    "GET /products"
  ],
  "log_error_codes": [400, 401, 403, 404, 500, 502, 503],
  "proxied_endpoints": [
    "GET /*"
  ],

  "log_headers": false,
  "forbidden_headers": ["Authorization", "Cookie", "X-Api-Key"],

  "log_file": "./logs/proxy.log",
  "log_level": "info",
  "max_size_mb": 50,
  "max_backups": 10,
  "max_age_days": 30,
  "compress": true,

  "health_endpoint": "GET /health",
  "allowed_cidrs": [],
  "deny_by_default": false,
  "drop_bad_connections": true,
  "shutdown_timeout_seconds": 25,
  "port": 8080
}
⁠Route modes
ModeFieldBehaviour
Full logginglogged_endpointsEvery request and response is logged
Error-only loggingerror_endpointsSilent on success; logs request + response only when backend returns a code from log_error_codes
Silent passthroughproxied_endpointsProxied without any logging. Use GET /* as catch-all

Priority when a request matches multiple lists: logged_endpoints → error_endpoints → proxied_endpoints.

⁠Path patterns
PatternMatches
GET /postsOnly /posts (exact)
GET /posts/*/posts, /posts/1, /posts/1/comments, …
GET /Only / (exact)
GET /*Any path — catch-all
⁠Config reference
FieldTypeDescription
target_urlstringBackend base URL
logged_endpoints[]stringMETHOD /path — proxied with full logging
error_endpoints[]stringMETHOD /path — logged only on matching error codes
log_error_codes[]intHTTP status codes that trigger logging for error_endpoints
proxied_endpoints[]stringMETHOD /path — proxied silently, no logs
log_levelstringdebug, info, warn, error
log_headersboolInclude request headers in logs
forbidden_headers[]stringHeaders stripped from logs (traffic is unaffected)
log_filestringPath to log file
max_size_mbintMax log file size before rotation
max_backupsintNumber of rotated files to keep
max_age_daysintMax age of rotated files
compressboolGzip rotated log files
health_endpointstringMETHOD /path on the backend to probe for /gate-health. Omit to skip backend check
allowed_cidrs[]stringAllowlisted IP ranges. Empty = allow all
deny_by_defaultboolBlock all IPs not in allowed_cidrs
drop_bad_connectionsboolLog count of TCP-dropped connections every 10s
shutdown_timeout_secondsintGraceful shutdown window
portintPort auditgate listens on

⁠Log format

Each request produces two log lines — request on arrival, response on completion:

{"level":"info","hostname":"346acb56e7ca","service":"auditgate","version":"dev","env":"local","request_id":"d17060f0-8e6f-4ece-adff-6b7cdf06cf46","method":"GET","path":"/posts/1","remote_addr":"172.19.0.1:56188","time":"2026-07-04T13:26:14.274182644+03:00","event":"request"}
{"level":"info","hostname":"346acb56e7ca","service":"auditgate","version":"dev","env":"local","request_id":"d17060f0-8e6f-4ece-adff-6b7cdf06cf46","method":"GET","path":"/posts/1","backend_status_code":200,"dispatch_ms":0,"backend_duration_ms":467,"total_duration_ms":467,"proxy_overhead_ms":0,"client_status_code":200,"backend_response_body":"{...}","time":"2026-07-04T13:26:14.742045074+03:00","event":"response"}

On client disconnect (-m 0.1 adds 100ms timeout) — curl -m 0.1 http://localhost:8080/posts/1:

{"level":"info","hostname":"346acb56e7ca","service":"auditgate","version":"dev","env":"local","request_id":"949fe0f0-6619-49b3-b623-4481ca779fc5","method":"GET","path":"/posts/1","remote_addr":"172.19.0.1:35132","time":"2026-07-04T13:26:50.429669751+03:00","event":"request"}
{"level":"warn","hostname":"346acb56e7ca","service":"auditgate","version":"dev","env":"local","client_status":"disconnected","client_alive_ms":99,"request_id":"949fe0f0-6619-49b3-b623-4481ca779fc5","method":"GET","path":"/posts/1","backend_status_code":200,"dispatch_ms":0,"backend_duration_ms":135,"total_duration_ms":135,"proxy_overhead_ms":0,"backend_response_body":"{...}","time":"2026-07-04T13:26:50.565285618+03:00","event":"response"}
⁠Timing fields
FieldDescription
dispatch_msTime from request arrival to the moment the backend call is fired — proxy-only cost
backend_duration_msPure backend latency (network + server processing)
total_duration_msWall time for the full round trip
proxy_overhead_mstotal - backend — everything the proxy added

⁠Health check

GET /gate-health is always available and never proxied or logged.

curl http://localhost:8080/gate-health

When backend is reachable — HTTP 200:

{"status":"ok","backend":"ok"}

When backend is down or returns 5xx — HTTP 503:

{"status":"degraded","backend":"unreachable"}

Backend is probed using the method and path defined in health_endpoint. If health_endpoint is omitted, only auditgate itself is checked and backend is always "ok".


⁠Grafana + Loki integration

Auditgate JSON logs can be shipped to Loki via the Loki Docker Driver and visualised in Grafana with zero configuration. A ready-to-use Docker Compose stack with a pre-built Grafana dashboard is available in the auditgate-stack⁠ repository.

The dashboard includes request rate, error rate, backend latency percentiles (p50/p95/p99), proxy overhead, top paths by request count, slowest paths by average latency, and a live log stream.


Tag summary

Content type

Image

Digest

sha256:449bdea45…

Size

9.1 MB

Last updated

3 months ago

docker pull ygolovnia/auditgate