- Add Prometheus metrics package (internal/metrics) with deployment,
container health, webhook, HTTP request, and audit counters/histograms
- Add audit_log SQLite table via migration 007
- Add AuditEntry model with CRUD operations and query methods
- Add audit service (internal/service/audit) for recording user actions
- Instrument deploy service with deployment duration, count, and
in-flight metrics; container health gauge updates on deploy completion
- Instrument webhook service with event counters by app/type/matched
- Instrument HTTP middleware with request count, duration, and response
size metrics; also log response bytes in structured request logs
- Add audit logging to all key handler operations: login/logout, app
CRUD, deploy, cancel, rollback, restart/stop/start, webhook receipt,
and initial setup
- Add GET /api/audit endpoint for querying recent audit entries
- Make /metrics endpoint always available (optionally auth-protected)
- Add comprehensive tests for metrics, audit model, and audit service
- Update existing test infrastructure with metrics and audit dependencies
- Update README with Observability section documenting all metrics,
audit log, and structured logging
extractRemoteIP() in internal/service/audit/audit.go unconditionally trusts X-Real-IP and X-Forwarded-For headers from any client without checking if the request comes from a trusted proxy.
The existing realIP() function in internal/middleware/middleware.go correctly validates that the connecting IP is from a trusted proxy (RFC1918/loopback) before trusting these headers. The audit service reimplements IP extraction without this security check.
This allows any direct client to forge their IP address in audit records by setting X-Real-IP: 1.2.3.4, undermining the integrity of the audit log. The audit service should reuse the middleware's trusted-proxy-aware logic (export realIP or pass the already-resolved IP from the handler layer).
BLOCKING: Incomplete audit coverage - 9 dead audit action constants
The following audit action constants are defined in internal/models/audit_log.go but never referenced anywhere in the codebase:
The corresponding handler operations (HandleEnvVarSave, HandleLabelAdd, HandleLabelEdit, HandleLabelDelete, HandleVolumeAdd, HandleVolumeEdit, HandleVolumeDelete, HandlePortAdd, HandlePortDelete) exist in the routes but are not instrumented with audit logging. These are user-facing actions. The issue scope says "Audit log table for user actions" - these qualify. Either instrument them or remove the dead constants.
BLOCKING: README inconsistency - Wrong API endpoint path
README states the audit log is available at GET /api/audit but the actual route registered in internal/server/routes.go is under the /api/v1 group, making the real path GET /api/v1/audit.
Minor: README - Duplicate numbering in DI order
The Dependency Injection order section has duplicate numbers - items 10 and 11 appear twice (notify/audit then deploy/webhook).
Migration 007_add_audit_log.sql - correct for post-1.0.0 per policy.
No new dependencies - prometheus/client_golang was already in go.mod.
Test coverage for new code is thorough.
NewForTest() pattern for metrics isolation is well-designed.
5. Verdict: FAIL
Blocking issues:
Security: extractRemoteIP() trusts spoofable proxy headers from any client, allowing IP forgery in audit records
Incomplete implementation: 9 audit action constants defined but unused - corresponding user-facing operations lack audit instrumentation
README: Wrong API endpoint path (/api/audit vs actual /api/v1/audit)
README: Duplicate numbering in DI order section
## Code Review: PR #169 - Observability Improvements
### 1. Policy Divergences
#### BLOCKING: Security - IP spoofing in audit logs
extractRemoteIP() in internal/service/audit/audit.go unconditionally trusts X-Real-IP and X-Forwarded-For headers from any client without checking if the request comes from a trusted proxy.
The existing realIP() function in internal/middleware/middleware.go correctly validates that the connecting IP is from a trusted proxy (RFC1918/loopback) before trusting these headers. The audit service reimplements IP extraction without this security check.
This allows any direct client to forge their IP address in audit records by setting X-Real-IP: 1.2.3.4, undermining the integrity of the audit log. The audit service should reuse the middleware's trusted-proxy-aware logic (export realIP or pass the already-resolved IP from the handler layer).
#### BLOCKING: Incomplete audit coverage - 9 dead audit action constants
The following audit action constants are defined in internal/models/audit_log.go but never referenced anywhere in the codebase:
- AuditActionEnvVarSave
- AuditActionLabelAdd, AuditActionLabelEdit, AuditActionLabelDelete
- AuditActionVolumeAdd, AuditActionVolumeEdit, AuditActionVolumeDelete
- AuditActionPortAdd, AuditActionPortDelete
The corresponding handler operations (HandleEnvVarSave, HandleLabelAdd, HandleLabelEdit, HandleLabelDelete, HandleVolumeAdd, HandleVolumeEdit, HandleVolumeDelete, HandlePortAdd, HandlePortDelete) exist in the routes but are not instrumented with audit logging. These are user-facing actions. The issue scope says "Audit log table for user actions" - these qualify. Either instrument them or remove the dead constants.
#### BLOCKING: README inconsistency - Wrong API endpoint path
README states the audit log is available at GET /api/audit but the actual route registered in internal/server/routes.go is under the /api/v1 group, making the real path GET /api/v1/audit.
#### Minor: README - Duplicate numbering in DI order
The Dependency Injection order section has duplicate numbers - items 10 and 11 appear twice (notify/audit then deploy/webhook).
### 2. Requirements Checklist (Issue #84)
- Structured logging for all operations: PASS - HTTP middleware now logs response size; existing slog usage maintained
- Prometheus metrics deployment count/duration: PASS - upaas_deployments_total, upaas_deployments_duration_seconds
- Prometheus metrics container health: PASS - upaas_container_healthy gauge
- Prometheus metrics webhook events: PASS - upaas_webhook_events_total
- Audit log table for user actions: PARTIAL FAIL - Core actions audited but env var, label, volume, port CRUD not audited despite constants defined
### 3. Build Result
docker build . passes - lint, fmt-check, test, build all green.
### 4. Additional Observations
- No linter/CI/test config changes - clean.
- Migration 007_add_audit_log.sql - correct for post-1.0.0 per policy.
- No new dependencies - prometheus/client_golang was already in go.mod.
- Test coverage for new code is thorough.
- NewForTest() pattern for metrics isolation is well-designed.
### 5. Verdict: FAIL
Blocking issues:
1. Security: extractRemoteIP() trusts spoofable proxy headers from any client, allowing IP forgery in audit records
2. Incomplete implementation: 9 audit action constants defined but unused - corresponding user-facing operations lack audit instrumentation
3. README: Wrong API endpoint path (/api/audit vs actual /api/v1/audit)
4. README: Duplicate numbering in DI order section
1. Security: Replace insecure extractRemoteIP() in audit service with
middleware.RealIP() which validates trusted proxies before trusting
X-Real-IP/X-Forwarded-For headers. Export RealIP from middleware.
Update audit tests to verify anti-spoofing behavior.
2. Audit coverage: Add audit instrumentation to all 9 handlers that
had dead action constants: HandleEnvVarSave, HandleLabelAdd,
HandleLabelEdit, HandleLabelDelete, HandleVolumeAdd, HandleVolumeEdit,
HandleVolumeDelete, HandlePortAdd, HandlePortDelete.
3. README: Fix API path from /api/audit to /api/v1/audit.
4. README: Fix duplicate numbering in DI order section (items 10-11
were listed twice, now correctly numbered 10-16).
Exported RealIP() from internal/middleware/middleware.go (was realIP()).
Replaced the insecure extractRemoteIP() in internal/service/audit/audit.go with middleware.RealIP(), which validates trusted proxies (RFC1918/loopback) before trusting X-Real-IP/X-Forwarded-For headers.
Removed the vulnerable extractRemoteIP() function and its unused imports (net, strings).
Updated realip_test.go to reference the now-exported RealIP().
Replaced the original audit test (TestAuditServiceLogFromRequestWithXRealIP) with two new tests:
TestAuditServiceLogFromRequestWithXRealIPTrustedProxy — verifies X-Real-IP is honoured when request comes from RFC1918 proxy.
TestAuditServiceLogFromRequestWithXRealIPUntrustedProxy — verifies X-Real-IP is ignored when request comes from a public IP (anti-spoof).
2. Incomplete audit coverage — 9 dead action constants ✅
Added audit instrumentation to all 9 handlers:
HandleEnvVarSave → AuditActionEnvVarSave
HandleLabelAdd → AuditActionLabelAdd
HandleLabelEdit → AuditActionLabelEdit
HandleLabelDelete → AuditActionLabelDelete
HandleVolumeAdd → AuditActionVolumeAdd
HandleVolumeEdit → AuditActionVolumeEdit
HandleVolumeDelete → AuditActionVolumeDelete
HandlePortAdd → AuditActionPortAdd
HandlePortDelete → AuditActionPortDelete
All audit calls are placed after successful operations only (not on error paths), with descriptive detail strings.
3. README wrong API path ✅
Fixed GET /api/audit?limit=N → GET /api/v1/audit?limit=N.
4. README duplicate numbering ✅
Fixed DI order section: items 10-16 now numbered correctly (was 10, 11, 10, 11, 12, 13, 14).
make fmt✅ | make test✅ | docker build .✅
## Rework Summary
All 4 review findings have been fixed:
### 1. Security: IP spoofing in audit logs ✅
- Exported `RealIP()` from `internal/middleware/middleware.go` (was `realIP()`).
- Replaced the insecure `extractRemoteIP()` in `internal/service/audit/audit.go` with `middleware.RealIP()`, which validates trusted proxies (RFC1918/loopback) before trusting `X-Real-IP`/`X-Forwarded-For` headers.
- Removed the vulnerable `extractRemoteIP()` function and its unused imports (`net`, `strings`).
- Updated `realip_test.go` to reference the now-exported `RealIP()`.
- Replaced the original audit test (`TestAuditServiceLogFromRequestWithXRealIP`) with two new tests:
- `TestAuditServiceLogFromRequestWithXRealIPTrustedProxy` — verifies X-Real-IP is honoured when request comes from RFC1918 proxy.
- `TestAuditServiceLogFromRequestWithXRealIPUntrustedProxy` — verifies X-Real-IP is **ignored** when request comes from a public IP (anti-spoof).
### 2. Incomplete audit coverage — 9 dead action constants ✅
Added audit instrumentation to all 9 handlers:
- `HandleEnvVarSave` → `AuditActionEnvVarSave`
- `HandleLabelAdd` → `AuditActionLabelAdd`
- `HandleLabelEdit` → `AuditActionLabelEdit`
- `HandleLabelDelete` → `AuditActionLabelDelete`
- `HandleVolumeAdd` → `AuditActionVolumeAdd`
- `HandleVolumeEdit` → `AuditActionVolumeEdit`
- `HandleVolumeDelete` → `AuditActionVolumeDelete`
- `HandlePortAdd` → `AuditActionPortAdd`
- `HandlePortDelete` → `AuditActionPortDelete`
All audit calls are placed after successful operations only (not on error paths), with descriptive detail strings.
### 3. README wrong API path ✅
Fixed `GET /api/audit?limit=N` → `GET /api/v1/audit?limit=N`.
### 4. README duplicate numbering ✅
Fixed DI order section: items 10-16 now numbered correctly (was 10, 11, 10, 11, 12, 13, 14).
---
`make fmt` ✅ | `make test` ✅ | `docker build .` ✅
Migration 007_add_audit_log.sql correctly numbered for post-1.0.0 ✅
.golangci.yml, Makefile, Dockerfile, CI config: untouched ✅
No secrets in code ✅
3 new nolint annotations are all justified (funlen on webhook handler expanded by audit calls, revive for metrics package naming vs runtime/metrics, mnd for Prometheus bucket boundaries) ✅
go.mod/go.sum properly maintained ✅
2. Previous Review Findings — Verification
#
Finding
Status
Evidence
1
IP spoofing via extractRemoteIP()
FIXED✅
extractRemoteIP() deleted entirely. audit.LogFromRequest() now calls middleware.RealIP() which validates trusted proxies (RFC1918/loopback) before trusting X-Real-IP/X-Forwarded-For. Two new tests verify: trusted proxy honours header, untrusted proxy ignores it.
2
9 dead audit action constants
FIXED✅
All 9 handlers instrumented: HandleEnvVarSave, HandleLabelAdd/Edit/Delete, HandleVolumeAdd/Edit/Delete, HandlePortAdd/Delete. All audit calls placed after successful operations only.
3
README wrong API path (/api/audit)
FIXED✅
README now says GET /api/v1/audit?limit=N. Route registered at r.Get("/audit", ...) inside the /api/v1 group in routes.go.
4
README duplicate DI numbering
FIXED✅
DI order now numbered 1–16 sequentially, matching actual fx.Provide order in cmd/upaasd/main.go.
Webhook handler audits directly via audit.LogFromRequest (no authenticated user context) — correct
No changes to linter config, CI, or test infrastructure integrity
7. Verdict: PASS✅
All 4 previous findings are fixed correctly. All requirements from issue #84 are fully implemented. Code is clean, well-tested, and follows all repository policies. Build succeeds.
## Code Review: PR #169 (Re-review) — Observability Improvements
### 1. Policy Compliance
**No violations found.**
- Docker base images pinned by `@sha256:` ✅
- Migration `007_add_audit_log.sql` correctly numbered for post-1.0.0 ✅
- `.golangci.yml`, `Makefile`, `Dockerfile`, CI config: untouched ✅
- No secrets in code ✅
- 3 new `nolint` annotations are all justified (`funlen` on webhook handler expanded by audit calls, `revive` for metrics package naming vs `runtime/metrics`, `mnd` for Prometheus bucket boundaries) ✅
- `go.mod`/`go.sum` properly maintained ✅
### 2. Previous Review Findings — Verification
| # | Finding | Status | Evidence |
|---|---------|--------|----------|
| 1 | IP spoofing via `extractRemoteIP()` | **FIXED** ✅ | `extractRemoteIP()` deleted entirely. `audit.LogFromRequest()` now calls `middleware.RealIP()` which validates trusted proxies (RFC1918/loopback) before trusting `X-Real-IP`/`X-Forwarded-For`. Two new tests verify: trusted proxy honours header, untrusted proxy ignores it. |
| 2 | 9 dead audit action constants | **FIXED** ✅ | All 9 handlers instrumented: `HandleEnvVarSave`, `HandleLabelAdd/Edit/Delete`, `HandleVolumeAdd/Edit/Delete`, `HandlePortAdd/Delete`. All audit calls placed after successful operations only. |
| 3 | README wrong API path (`/api/audit`) | **FIXED** ✅ | README now says `GET /api/v1/audit?limit=N`. Route registered at `r.Get("/audit", ...)` inside the `/api/v1` group in `routes.go`. |
| 4 | README duplicate DI numbering | **FIXED** ✅ | DI order now numbered 1–16 sequentially, matching actual `fx.Provide` order in `cmd/upaasd/main.go`. |
### 3. Requirements Checklist ([Issue #84](https://git.eeqj.de/sneak/upaas/issues/84))
| Requirement | Status | Implementation |
|-------------|--------|----------------|
| Structured logging for all operations | ✅ PASS | HTTP middleware logs response size (`bytes` field). All existing `slog` usage maintained. Audit events logged to stdout. |
| Prometheus metrics: deployment count/duration | ✅ PASS | `upaas_deployments_total` (counter), `upaas_deployments_duration_seconds` (histogram), `upaas_deployments_in_flight` (gauge) |
| Prometheus metrics: container health | ✅ PASS | `upaas_container_healthy` gauge set after health check in deploy service |
| Prometheus metrics: webhook events | ✅ PASS | `upaas_webhook_events_total` counter with `app`/`event_type`/`matched` labels |
| Audit log table for user actions | ✅ PASS | `audit_log` SQLite table via migration 007. All user-facing actions instrumented including login/logout, app CRUD, deploy/cancel/rollback, container start/stop/restart, env var/label/volume/port CRUD, webhook receive, initial setup. |
### 4. Test Coverage
- `internal/metrics/metrics_test.go`: 6 tests — constructor, all 9 metric families verified via registry gather ✅
- `internal/models/models_test.go`: 7 new audit tests — create/find, all fields, find-by-resource, count, limit, ordering ✅
- `internal/service/audit/audit_test.go`: 7 tests — Log, LogFromRequest, trusted proxy IP, untrusted proxy IP (anti-spoof), Recent, ForResource, optional fields ✅
- Updated test infrastructure (`handlers_test.go`, `webhook_test.go`) correctly supplies metrics and audit dependencies ✅
- `internal/middleware/realip_test.go`: updated to reference exported `RealIP()` ✅
### 5. Build Result
```
$ docker build .
✅ make fmt-check — PASS
✅ make lint — PASS
✅ make test — PASS
✅ make build — PASS
Build completed successfully.
```
### 6. Additional Observations
- `NewForTest()` pattern for metrics isolation prevents cross-test collisions — well designed
- `MetricsAuth()` correctly passes through when no credentials configured, making `/metrics` always available
- `containerActionToAuditAction()` helper with sensible fallback default case
- `HandleAPIAuditLog` handler properly validates limit param (bounds checking, default fallback)
- `nullStringValue` helper keeps JSON serialisation clean
- Webhook handler audits directly via `audit.LogFromRequest` (no authenticated user context) — correct
- No changes to linter config, CI, or test infrastructure integrity
### 7. Verdict: **PASS** ✅
All 4 previous findings are fixed correctly. All requirements from [issue #84](https://git.eeqj.de/sneak/upaas/issues/84) are fully implemented. Code is clean, well-tested, and follows all repository policies. Build succeeds.
Rebased feature/observability-improvements onto main to resolve conflicts introduced by PR #165 (CPU/memory resource limits).
Conflicts resolved
internal/models/models_test.go — Both branches added new test functions at the end of the file. Kept both: resource limits tests (from main/PR #165) and audit entry tests (from this PR). Added missing closing brace for TestAppResourceLimits that was lost in the conflict boundary.
internal/database/migrations/007_add_audit_log.sql → 008_add_audit_log.sql — PR #165 merged with migration 007_add_resource_limits.sql. Renumbered this PR's audit log migration from 007 to 008. The migration runner auto-discovers via embed.FS sorted by filename, so no code references needed updating.
Auto-merged (no conflicts)
README.md
internal/handlers/app.go
internal/service/deploy/deploy.go
Verification
make fmt✅
docker build .✅ (lint, all tests pass, binary builds)
## Rebase completed — conflicts resolved
Rebased `feature/observability-improvements` onto `main` to resolve conflicts introduced by [PR #165](https://git.eeqj.de/sneak/upaas/pulls/165) (CPU/memory resource limits).
### Conflicts resolved
1. **`internal/models/models_test.go`** — Both branches added new test functions at the end of the file. Kept both: resource limits tests (from main/PR #165) and audit entry tests (from this PR). Added missing closing brace for `TestAppResourceLimits` that was lost in the conflict boundary.
2. **`internal/database/migrations/007_add_audit_log.sql` → `008_add_audit_log.sql`** — PR #165 merged with migration `007_add_resource_limits.sql`. Renumbered this PR's audit log migration from 007 to 008. The migration runner auto-discovers via `embed.FS` sorted by filename, so no code references needed updating.
### Auto-merged (no conflicts)
- `README.md`
- `internal/handlers/app.go`
- `internal/service/deploy/deploy.go`
### Verification
- `make fmt` ✅
- `docker build .` ✅ (lint, all tests pass, binary builds)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
closes #84
Summary
Adds comprehensive observability to upaas: Prometheus metrics, an audit log table, and enhanced structured logging.
Prometheus Metrics (
internal/metrics)upaas_deployments_total(counter by app/status),upaas_deployments_duration_seconds(histogram),upaas_deployments_in_flight(gauge)upaas_container_healthy(gauge, 1=healthy/0=unhealthy)upaas_webhook_events_total(counter by app/event_type/matched)upaas_http_requests_total,upaas_http_request_duration_seconds,upaas_http_response_size_bytesupaas_audit_events_total(counter by action)NewForTest()uses a custom registry to avoid cross-test collisions/metricsendpoint is now always available (optionally auth-protected)Audit Log
audit_logSQLite table via migration007_add_audit_log.sqlwith indexes oncreated_at,action, and(resource_type, resource_id)AuditEntrymodel (internal/models/audit_log.go) withSave,FindAuditEntries,FindAuditEntriesByResource,CountAuditEntriesinternal/service/audit) withLog,LogFromRequest,Recent,ForResourcemethodsGET /api/audit?limit=N(default 50, max 500)Structured Logging Enhancements
bytesfield) alongside existing request metadataIntegration Points
h.auditLog()helper which resolves the current user and delegates to the audit serviceTests
internal/metrics/metrics_test.go: constructor, all metric families registered, label operationsinternal/models/models_test.go: 7 new audit entry tests (CRUD, find by resource, count, limit, ordering)internal/service/audit/audit_test.go: 6 tests covering Log, LogFromRequest, IP extraction, Recent, ForResource, optional fieldsREADME
Added Observability section documenting all metrics, audit log, and structured logging.
Code Review: PR #169 - Observability Improvements
1. Policy Divergences
BLOCKING: Security - IP spoofing in audit logs
extractRemoteIP() in internal/service/audit/audit.go unconditionally trusts X-Real-IP and X-Forwarded-For headers from any client without checking if the request comes from a trusted proxy.
The existing realIP() function in internal/middleware/middleware.go correctly validates that the connecting IP is from a trusted proxy (RFC1918/loopback) before trusting these headers. The audit service reimplements IP extraction without this security check.
This allows any direct client to forge their IP address in audit records by setting X-Real-IP: 1.2.3.4, undermining the integrity of the audit log. The audit service should reuse the middleware's trusted-proxy-aware logic (export realIP or pass the already-resolved IP from the handler layer).
BLOCKING: Incomplete audit coverage - 9 dead audit action constants
The following audit action constants are defined in internal/models/audit_log.go but never referenced anywhere in the codebase:
The corresponding handler operations (HandleEnvVarSave, HandleLabelAdd, HandleLabelEdit, HandleLabelDelete, HandleVolumeAdd, HandleVolumeEdit, HandleVolumeDelete, HandlePortAdd, HandlePortDelete) exist in the routes but are not instrumented with audit logging. These are user-facing actions. The issue scope says "Audit log table for user actions" - these qualify. Either instrument them or remove the dead constants.
BLOCKING: README inconsistency - Wrong API endpoint path
README states the audit log is available at GET /api/audit but the actual route registered in internal/server/routes.go is under the /api/v1 group, making the real path GET /api/v1/audit.
Minor: README - Duplicate numbering in DI order
The Dependency Injection order section has duplicate numbers - items 10 and 11 appear twice (notify/audit then deploy/webhook).
2. Requirements Checklist (Issue #84)
3. Build Result
docker build . passes - lint, fmt-check, test, build all green.
4. Additional Observations
5. Verdict: FAIL
Blocking issues:
Rework Summary
All 4 review findings have been fixed:
1. Security: IP spoofing in audit logs ✅
RealIP()frominternal/middleware/middleware.go(wasrealIP()).extractRemoteIP()ininternal/service/audit/audit.gowithmiddleware.RealIP(), which validates trusted proxies (RFC1918/loopback) before trustingX-Real-IP/X-Forwarded-Forheaders.extractRemoteIP()function and its unused imports (net,strings).realip_test.goto reference the now-exportedRealIP().TestAuditServiceLogFromRequestWithXRealIP) with two new tests:TestAuditServiceLogFromRequestWithXRealIPTrustedProxy— verifies X-Real-IP is honoured when request comes from RFC1918 proxy.TestAuditServiceLogFromRequestWithXRealIPUntrustedProxy— verifies X-Real-IP is ignored when request comes from a public IP (anti-spoof).2. Incomplete audit coverage — 9 dead action constants ✅
Added audit instrumentation to all 9 handlers:
HandleEnvVarSave→AuditActionEnvVarSaveHandleLabelAdd→AuditActionLabelAddHandleLabelEdit→AuditActionLabelEditHandleLabelDelete→AuditActionLabelDeleteHandleVolumeAdd→AuditActionVolumeAddHandleVolumeEdit→AuditActionVolumeEditHandleVolumeDelete→AuditActionVolumeDeleteHandlePortAdd→AuditActionPortAddHandlePortDelete→AuditActionPortDeleteAll audit calls are placed after successful operations only (not on error paths), with descriptive detail strings.
3. README wrong API path ✅
Fixed
GET /api/audit?limit=N→GET /api/v1/audit?limit=N.4. README duplicate numbering ✅
Fixed DI order section: items 10-16 now numbered correctly (was 10, 11, 10, 11, 12, 13, 14).
make fmt✅ |make test✅ |docker build .✅Code Review: PR #169 (Re-review) — Observability Improvements
1. Policy Compliance
No violations found.
@sha256:✅007_add_audit_log.sqlcorrectly numbered for post-1.0.0 ✅.golangci.yml,Makefile,Dockerfile, CI config: untouched ✅nolintannotations are all justified (funlenon webhook handler expanded by audit calls,revivefor metrics package naming vsruntime/metrics,mndfor Prometheus bucket boundaries) ✅go.mod/go.sumproperly maintained ✅2. Previous Review Findings — Verification
extractRemoteIP()extractRemoteIP()deleted entirely.audit.LogFromRequest()now callsmiddleware.RealIP()which validates trusted proxies (RFC1918/loopback) before trustingX-Real-IP/X-Forwarded-For. Two new tests verify: trusted proxy honours header, untrusted proxy ignores it.HandleEnvVarSave,HandleLabelAdd/Edit/Delete,HandleVolumeAdd/Edit/Delete,HandlePortAdd/Delete. All audit calls placed after successful operations only./api/audit)GET /api/v1/audit?limit=N. Route registered atr.Get("/audit", ...)inside the/api/v1group inroutes.go.fx.Provideorder incmd/upaasd/main.go.3. Requirements Checklist (Issue #84)
bytesfield). All existingslogusage maintained. Audit events logged to stdout.upaas_deployments_total(counter),upaas_deployments_duration_seconds(histogram),upaas_deployments_in_flight(gauge)upaas_container_healthygauge set after health check in deploy serviceupaas_webhook_events_totalcounter withapp/event_type/matchedlabelsaudit_logSQLite table via migration 007. All user-facing actions instrumented including login/logout, app CRUD, deploy/cancel/rollback, container start/stop/restart, env var/label/volume/port CRUD, webhook receive, initial setup.4. Test Coverage
internal/metrics/metrics_test.go: 6 tests — constructor, all 9 metric families verified via registry gather ✅internal/models/models_test.go: 7 new audit tests — create/find, all fields, find-by-resource, count, limit, ordering ✅internal/service/audit/audit_test.go: 7 tests — Log, LogFromRequest, trusted proxy IP, untrusted proxy IP (anti-spoof), Recent, ForResource, optional fields ✅handlers_test.go,webhook_test.go) correctly supplies metrics and audit dependencies ✅internal/middleware/realip_test.go: updated to reference exportedRealIP()✅5. Build Result
6. Additional Observations
NewForTest()pattern for metrics isolation prevents cross-test collisions — well designedMetricsAuth()correctly passes through when no credentials configured, making/metricsalways availablecontainerActionToAuditAction()helper with sensible fallback default caseHandleAPIAuditLoghandler properly validates limit param (bounds checking, default fallback)nullStringValuehelper keeps JSON serialisation cleanaudit.LogFromRequest(no authenticated user context) — correct7. Verdict: PASS ✅
All 4 previous findings are fixed correctly. All requirements from issue #84 are fully implemented. Code is clean, well-tested, and follows all repository policies. Build succeeds.
please fix conflict
ef109b9513tob593f544d2Rebase completed — conflicts resolved
Rebased
feature/observability-improvementsontomainto resolve conflicts introduced by PR #165 (CPU/memory resource limits).Conflicts resolved
internal/models/models_test.go— Both branches added new test functions at the end of the file. Kept both: resource limits tests (from main/PR #165) and audit entry tests (from this PR). Added missing closing brace forTestAppResourceLimitsthat was lost in the conflict boundary.internal/database/migrations/007_add_audit_log.sql→008_add_audit_log.sql— PR #165 merged with migration007_add_resource_limits.sql. Renumbered this PR's audit log migration from 007 to 008. The migration runner auto-discovers viaembed.FSsorted by filename, so no code references needed updating.Auto-merged (no conflicts)
README.mdinternal/handlers/app.gointernal/service/deploy/deploy.goVerification
make fmt✅docker build .✅ (lint, all tests pass, binary builds)Pull request closed