Commit 7390cff
* feat(#91): agent — phase 4/5/7 metrics collection (nginx, Apache, Caddy, Traefik) + access-log endpoint
Builds on PR #100's detection layer with periodic metric scraping for
the four web servers plus a structured access-log endpoint. This
finishes the agent-side work for issue #91; the dashboard side
(per-source chart cards, multi-source Error Insights, DB migration)
ships as the next PR.
Per-source collectors (each adds a CollectorGear collector +
/api/v1/{name}/stats endpoint, cached snapshot, force=true synchronous
re-scrape):
- nginx: parses stub_status (active/reading/writing/waiting +
monotonic accepts/handled/requests).
- apache: parses mod_status?auto's key-value format (Total Accesses,
worker pool, CPU load, ReqPerSec, etc.).
- caddy: scrapes Prometheus at :2019/metrics; sums
caddy_http_requests_total + request_errors_total; flags admin status
via caddy_admin_http_requests_total presence.
- traefik: scrapes Prometheus; buckets traefik_router_requests_total by
status-class label so the dashboard gets a real 2xx/3xx/4xx/5xx
breakdown; also surfaces the entrypoints list.
Access-log endpoint (Phase 5):
- New internal/framework/services/accesslog/ package with 5 profile
parsers: haproxy, nginx-combined, apache-common, apache-combined,
caddy-json. The dashboard's existing parseHAProxyLogLine is
ported into the haproxy profile byte-for-byte (with one regex
tightening: the syslog [pid] bracket no longer claims the date
match).
- New internal/gears/accesslog/ gear: GET
/api/v1/access-log/{source}/recent?status_min=500&limit=500 reads
the last N lines of the source's access log via tail, parses each
line with the matching profile, filters by status_min, returns
newest-first.
- 4 new env vars to override default log paths (HAPROXY_ACCESS_LOG,
NGINX_ACCESS_LOG, APACHE_ACCESS_LOG, CADDY_ACCESS_LOG). Apache
falls back from /var/log/apache2/ to /var/log/httpd/ for RHEL
hosts. Capability manifest reports which sources have a readable
log on this host.
Shared helper: internal/framework/services/promtext/ — minimal
Prometheus exposition-format parser (samples + label maps; counter
sums; SumByNameWithLabel for status-class extraction). Scoped to the
agent's needs to avoid pulling in prometheus/common's 50+-package
transitive footprint just for two scrape routines.
Test coverage:
- Each new collector has unit tests covering parser correctness,
scrape success / failure modes, 503 before first scrape, cached
response shape, force=true behaviour, override resolution.
- Access-log gear tests cover probe verdict, capabilities map,
unknown source 404, no-log available=false envelope, status_min
filtering, limit cap, tail-failure surfacing, isReadable's
non-regular-file rejection.
- 5 parser profiles each have happy-path + reject-noise tests
including HAProxy negative-Tt handling and Caddy non-HTTP entries.
- promtext tests cover summation, label-value escapes, malformed
lines, trailing scrape timestamps.
23 files added, 7 modified. go build / vet / test / gofmt all clean.
Out of scope (Phase 6, 8 + dashboard wire-up):
- DB migration adding source column to traffic_flows.
- Multi-source Error Insights (dashboard refactor).
- Cross-source aggregates (Phase 8 — optional).
- Source-aware ingest from these endpoints to traffic_flows.
- Per-source chart cards / KPIs / capability gates.
These all live in the dashboard repo and ship as the next PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#91): address Copilot review findings on PR #101
- access-log: implement the documented Apache CLF fallback. Previously
the handler used a single parser from sourceProfile (ApacheCombined)
and the comments + PR body claimed a per-record fallback to
ApacheCommon that didn't exist. RHEL hosts running default CLF would
have produced zero parsed records. New parseWithFallback helper +
sourceFallbackProfile map drive the actual fallback now; Apache is
the only source using it today.
- access-log: status_min query parameter now accepts an explicit 0 to
disable the filter. Was previously clamped to a 100 minimum, which
silently coerced 0 to 100 and broke the "give me all records"
intent. Default when the param is absent stays 500 (the dashboard's
primary use case). Lock the new defaults in via two new tests.
- caddy: drop the AdminRunning field. The previous heuristic relied on
caddy_admin_http_requests_total existing, which Prometheus doesn't
emit for counters with zero increments. A freshly-started Caddy
with admin enabled but no admin traffic yet would have falsely read
"admin disconnected." The real signal is "did the scrape succeed?"
— which the handler already conveys via 503 before the first
successful scrape — so the field was redundant on success and
misleading on cold start.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8b9b469 commit 7390cff
26 files changed
Lines changed: 3540 additions & 32 deletions
File tree
- gearbox-agent
- cmd/gearbox-agent
- internal
- framework
- config
- gear
- services
- accesslog
- promtext
- gears
- accesslog
- apache
- caddy
- nginx
- traefik
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
397 | 398 | | |
398 | 399 | | |
399 | 400 | | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
400 | 405 | | |
401 | 406 | | |
402 | 407 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
104 | 115 | | |
105 | 116 | | |
106 | 117 | | |
| |||
212 | 223 | | |
213 | 224 | | |
214 | 225 | | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
215 | 233 | | |
216 | 234 | | |
217 | 235 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
69 | 78 | | |
70 | 79 | | |
71 | 80 | | |
| |||
Lines changed: 89 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
Lines changed: 85 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
Lines changed: 82 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
0 commit comments