# Runtime Info And Ingress Metrics

GoClaw keeps lightweight process-local status for the gateway, the webhook listener, the ingress layer, ingress access logs and webhook audit logs in `internal/runtimeinfo/runtimeinfo.go`. HTTP handlers expose these snapshots to the Web UI and CLI status checks.

## Runtime State

| Area | Stored fields | Max retained |
|---|---|---|
| Gateway | bind mode, listen address, rate-limit settings, request counters | Current counters |
| Webhook listener | where `/goclaw/hook/` is served: listen address, port, bind mode, whether it is a separate port from the Web UI | Current values |
| Ingress | enabled flag, provider, source, public URL, upstream, allowlist, counters, last request time | Current counters |
| Ingress access log | timestamp, client IP, method, path, status code, duration, host, source | 200 entries |
| Webhook audit log | channel, event type, author, peer, preview, status | 500 entries |

The state is protected by a package-level mutex. `SnapshotState()` returns a copy of slices so callers do not mutate shared state.

## API Surface

| Endpoint | Handler | Data |
|---|---|---|
| `GET /api/stats` | `HandleStats` | Gateway status plus `BuildRuntimeStats()` |
| `GET /api/gateway?limit=30` | `HandleGateway` | `BuildRuntimeStats()` plus `access_logs`: the recent ingress access logs, newest first (`limit` default 30, at most 200) |
| `GET /api/audit?channel=&event=&user=&limit=` | `HandleAuditLogs` | Filtered webhook audit logs |

`/api/stats` includes these runtime sections:

```json
{
  "gateway_runtime": {
    "bind_mode": "loopback",
    "listen_addr": "127.0.0.1:8080",
    "rate_limit_enabled": true,
    "rate_limit_per_min": 60,
    "requests_total": 120,
    "rate_limited_total": 2
  },
  "public_ingress": {
    "enabled": true,
    "provider": "ngrok",
    "source": "gateway",
    "public_url": "https://example.ngrok.app",
    "configured_url": "https://example.ngrok.app",
    "upstream": "http://127.0.0.1:8080",
    "allow_preset": "",
    "allowlist": [],
    "forwarded_total": 40,
    "error_total": 1,
    "denied_total": 0,
    "last_request_at": "2026-06-20T08:45:00Z"
  },
  "webhook_listener": {
    "listen_addr": "127.0.0.1:8080",
    "port": "8080",
    "bind_mode": "loopback",
    "separate_port": false,
    "hook_path": "/goclaw/hook/",
    "webui_port": "8080"
  },
  "webhooks": {
    "msteams": { "enabled": false, "listen_addr": ":8080", "require_mention": false }
  },
  "webhook_secret_slug": "",
  "facebook_enabled": false
}
```

`upstream` is where a reverse proxy or ngrok must forward to: the webhook
listener on `http://127.0.0.1:<port>`, not the public URL. `configured_url`
is `gateway.ingress.url` as written in the config. `webhook_listener` says
where the hook routes are actually served; with `gateway.ingress.port` set
they move to their own listener (`separate_port: true`) and the Web UI port
answers them with 404.

## Update Points

- `gateway.AuthMiddleware` records gateway requests.
- `gateway.RateLimitMiddleware` records rate-limited requests.
- `cmd/gateway/main.go` records the gateway settings (`SetGateway`) and the webhook listener (`SetWebhookListener`) at start-up.
- `gateway.Ingress` records ingress state, allowlist entries and the denied counter.
- `gateway.AccessLogMiddleware` records the access log and the forwarded/error counters, whether or not an ingress is configured: every request on the ingress listener, and on the Web UI listener only requests to webhook paths.
- Channel handlers record semantic webhook audit entries through `runtimeinfo.RecordWebhookAudit`.

## Implementation Files

- `internal/runtimeinfo/runtimeinfo.go`
- `internal/gateway/handler_api.go`
- `internal/gateway/ingress.go`
- `internal/gateway/access_log.go`
- `internal/gateway/middleware.go`
- `cmd/gateway/main.go`
- `cmd/gateway/startup.go`
