🔥 feat: Add Uptime Middleware#2038
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new ChangesUptime middleware
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/uptime/config.go`:
- Around line 148-156: The threshold validation in Config validation only checks
ordering, so c.UI.GreenThreshold and c.UI.YellowThreshold can still accept
values outside the [0, 1] range. Update the validation logic in the Config
handling path to reject any out-of-range UI thresholds before the relative
comparison, keeping the existing error flow in place. Use the existing Config,
c.UI.GreenThreshold, and c.UI.YellowThreshold checks as the location to add the
percentage bounds validation.
In `@v3/uptime/internal/storage/sqlite.go`:
- Around line 296-299: ensureSQLiteDir is treating all file: URIs like in-memory
databases, which skips creating parent directories for disk-backed SQLite paths.
Update ensureSQLiteDir to special-case only :memory:, then for file: URIs strip
the scheme and any query string before running the existing MkdirAll logic so
on-disk targets like file:tmp/uptime.db?cache=shared get their parent directory
created. Use ensureSQLiteDir, strings.HasPrefix, and the directory creation path
to make the fix consistent with normal filesystem paths.
In `@v3/uptime/README.md`:
- Line 18: The preview image link in the README appears to contain a typo in the
repository name, using gofurry instead of gofiber. Update the Uptime dashboard
preview URL in the README image reference so it points to the correct host, and
verify the markdown image renders properly after the change.
In `@v3/uptime/status.go`:
- Around line 137-142: The `buildStatus` path in `Uptime` leaves stale storage
errors behind after a successful status query, so `storageStatus()` can keep
reporting degraded health. Update the successful return path in `buildStatus` to
clear the last recorded error by calling the existing `clearLastError` helper
before returning the `StatusResponse`, so current storage health is reflected
immediately.
In `@v3/uptime/ui.go`:
- Line 596: The page-scroll dock button’s accessible name should include both
the scroll progress and the scroll-up action. Update the button in the
page-scroll-dock markup so its aria-label/title still communicate that it is an
actionable control, using the existing scroll progress value together with
“scroll up”; apply the same fix wherever the dock label is set, including the
related page-scroll-dock update points.
- Around line 353-360: The focus styling for the uptime bars is removing the
keyboard focus indicator, making `.bar:focus-visible` hard to see. Update the
shared `.bar` hover/focus/active styles in the uptime UI so `:focus-visible`
keeps a clear visible outline or equivalent focus ring, while leaving the hover
and active visual treatment separate. Use the `.bar` selector group to locate
and adjust the focus-visible rule.
- Around line 619-623: The refresh flow in the uptime UI can overlap and hang
because it has no in-flight guard or request timeout. Update the refresh() logic
to track an active request and return early if one is already running, then add
AbortController-based timeout handling so slow API calls are cancelled instead
of piling up. Use the existing refresh() path and related status/state variables
like currentStatus, lastStatus, lastSuccessAt, and activeBar to place the guard
and abort cleanup in the right spot.
In `@v3/uptime/uptime_test.go`:
- Line 130: The dashboard date assertion is using the wrong day/month order and
should match year-month-day formatting. Update the UI date formatter where the
dashboard date string is built so it uses month before day, and adjust the
uptime test in uptime_test.go to assert against the corrected formatter output.
Use the existing date formatting logic and the test helper assertion around
bodyText to verify the new YYYY-MM-DD format.
In `@v3/uptime/uptime.go`:
- Around line 198-200: The HEAD shortcut in the uptime handler is bypassing the
same work that GET performs, so change the logic in the uptime request handler
to avoid returning early for fiber.MethodHead and instead run the
CachedSnapshot/dashboard rendering path first, only omitting the response body
at the end. Update the HEAD handling around the existing fiber.MethodHead checks
so any failure in snapshot loading or dashboard rendering is surfaced the same
way as GET, while still returning a HEAD-appropriate empty response on success.
- Around line 70-72: The uptime store initialization path in the startup logic
leaves the store open when `store.Init(ctx)` fails, unlike later failure paths
that clean up. Update the `uptime` setup flow so the `store` is closed before
returning from the `Init` error branch, using the existing `store.Close` cleanup
pattern in the same function to avoid leaking the SQLite handle or lock.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7071a71d-325b-4731-abb3-24f577275198
⛔ Files ignored due to path filters (3)
.github/workflows/test-uptime.ymlis excluded by!**/*.ymlv3/uptime/go.modis excluded by!**/*.modv3/uptime/go.sumis excluded by!**/*.sum,!**/*.sum
📒 Files selected for processing (10)
README.mdv3/README.mdv3/uptime/README.mdv3/uptime/config.gov3/uptime/internal/storage/sqlite.gov3/uptime/internal/storage/types.gov3/uptime/status.gov3/uptime/ui.gov3/uptime/uptime.gov3/uptime/uptime_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
- GitHub Check: lint / lint (v3/uptime)
- GitHub Check: Tests (1.26.x)
🔇 Additional comments (4)
README.md (1)
37-37: LGTM!v3/README.md (1)
35-35: LGTM!v3/uptime/README.md (2)
137-142: Verify handler routing behavior claims.The README statement is correct. The
Handler()implementation inv3/uptime/uptime.goexplicitly validates requests:
- Lines 176‑178: Non‑GET/HEAD methods receive
405 Method Not Allowedwith theAllow: GET, HEADheader.- Lines 181‑190: Paths other than the dashboard root or
/api/statusreturn404 Not Found.No changes are needed; the documented behavior matches the implementation.
91-92: Documentation accurately reflects implementationThe claims in the README are verified against the codebase:
- "one open connection":
sqlite.goexplicitly callsSetMaxOpenConns(1).Snapshotdefaults:DisableCachedefaults tofalse(cache enabled) and logic inconfig.gosetsCacheTTLtoSampleIntervalif undefined.UIdefaults:defaultGreenThresholdis0.999(99.9%) anddefaultYellowThresholdis0.99(99%).No corrections needed.
|
@gofurry pls check #2038 (comment) |
|
@gofurry I would suggest using https://github.com/gofiber/storage/tree/main/sqlite3 to create the sqlite db/connection and the using the |
Added system-based dark mode using prefers-color-scheme, without adding a theme switcher. The dashboard now keeps the light UI as the default and automatically adapts to the user's system preference.
|
Thanks for the suggestion. I checked Before I switch the uptime storage to use it via |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/uptime/dashboard.gohtml`:
- Around line 591-593: The scroll button markup in the dashboard template mixes
plain button semantics with range-widget ARIA state; remove the aria-valuemin,
aria-valuemax, and aria-valuenow attributes from the page-scroll-dock control in
the template so it remains a standard button. Keep the accessible label/text on
the page-scroll-dock and page-scroll-dock-value elements for progress
information, and apply the same cleanup to the other matching scroll button
instance mentioned in the review.
- Around line 898-903: The scrollUpQuarter handler in the dashboard template
still forces smooth scrolling even when reduced-motion is preferred. Update the
scroll action to check the prefers-reduced-motion media query and use "auto"
instead of "smooth" when it matches, while keeping the existing behavior
otherwise. Refer to scrollUpQuarter and the window.scrollTo call to make the
change in the JS path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3f9c601f-ac6e-41e4-bde3-ec146f9d5e26
📒 Files selected for processing (8)
v3/uptime/config.gov3/uptime/dashboard.gohtmlv3/uptime/internal/storage/sqlite.gov3/uptime/internal/storage/sqlite_test.gov3/uptime/status.gov3/uptime/ui.gov3/uptime/uptime.gov3/uptime/uptime_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
- v3/uptime/config.go
- v3/uptime/status.go
- v3/uptime/internal/storage/sqlite.go
- v3/uptime/uptime.go
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: lint / lint (v3/coraza)
⚠️ CI failures not shown inline (4)
GitHub Actions: Test Uptime / Tests (1.26.x): feat: add uptime middleware
Conclusion: failure
##[group]Run case "2" in
�[36;1mcase "2" in�[0m
�[36;1m ''|0)�[0m
�[36;1m # Reruns disabled: identical to the plain gotestsum invocation.�[0m
�[36;1m go run gotest.tools/gotestsum@v1.13.0 -f testname -- -race -count=1�[0m
�[36;1m ;;�[0m
�[36;1m *[!0-9]*)�[0m
�[36;1m echo "::error::rerun-fails must be a non-negative integer, got '2'"�[0m
GitHub Actions: Test Uptime / 0_Tests (1.26.x).txt: feat: add uptime middleware
Conclusion: failure
##[group]Run case "2" in
�[36;1mcase "2" in�[0m
�[36;1m ''|0)�[0m
�[36;1m # Reruns disabled: identical to the plain gotestsum invocation.�[0m
�[36;1m go run gotest.tools/gotestsum@v1.13.0 -f testname -- -race -count=1�[0m
�[36;1m ;;�[0m
�[36;1m *[!0-9]*)�[0m
�[36;1m echo "::error::rerun-fails must be a non-negative integer, got '2'"�[0m
GitHub Actions: Test Uptime / 1_Tests (1.25.x).txt: feat: add uptime middleware
Conclusion: failure
##[group]Run case "2" in
�[36;1mcase "2" in�[0m
�[36;1m ''|0)�[0m
�[36;1m # Reruns disabled: identical to the plain gotestsum invocation.�[0m
�[36;1m go run gotest.tools/gotestsum@v1.13.0 -f testname -- -race -count=1�[0m
�[36;1m ;;�[0m
�[36;1m *[!0-9]*)�[0m
�[36;1m echo "::error::rerun-fails must be a non-negative integer, got '2'"�[0m
GitHub Actions: Test Uptime / Tests (1.25.x): feat: add uptime middleware
Conclusion: failure
##[group]Run case "2" in
�[36;1mcase "2" in�[0m
�[36;1m ''|0)�[0m
�[36;1m # Reruns disabled: identical to the plain gotestsum invocation.�[0m
�[36;1m go run gotest.tools/gotestsum@v1.13.0 -f testname -- -race -count=1�[0m
�[36;1m ;;�[0m
�[36;1m *[!0-9]*)�[0m
�[36;1m echo "::error::rerun-fails must be a non-negative integer, got '2'"�[0m
🔇 Additional comments (3)
v3/uptime/internal/storage/sqlite_test.go (1)
9-21: LGTM!v3/uptime/ui.go (1)
5-5: LGTM!Also applies to: 53-54
v3/uptime/uptime_test.go (1)
84-103: LGTM!Also applies to: 150-155, 180-196, 234-250, 468-489, 583-587, 617-648
There was a problem hiding this comment.
Pull request overview
Adds a new Fiber v3 uptime middleware package that records periodic heartbeats, stores/query uptime history via a Redis-backed store, and exposes a built-in HTML dashboard plus /api/status JSON endpoint. It also introduces a dedicated CI workflow and comprehensive tests for config validation, snapshot/status calculation, handler behavior, and storage lifecycle.
Changes:
- Implemented the uptime runtime, handler routing, snapshot/status model, and embedded dashboard renderer.
- Added a Redis-backed storage implementation (with rollups + retention cleanup) under
internal/storage. - Added extensive unit tests, module files (
go.mod/go.sum), docs, and a dedicated GitHub Actions workflow.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/uptime/uptime.go | Core middleware runtime: background recording, maintenance, and handler routing. |
| v3/uptime/uptime_test.go | Runtime/handler/config tests (routing, HEAD/GET, snapshot errors, ID generator, probes). |
| v3/uptime/ui.go | Dashboard HTML rendering using embedded template + JSON payload injection. |
| v3/uptime/status.go | Snapshot/status model and calculations (day ranges, expected slots, current up/down). |
| v3/uptime/status_test.go | Tests for expected-slot calculations (including DST transitions). |
| v3/uptime/dashboard.gohtml | Embedded dashboard template (CSS/JS) for rendering uptime UI. |
| v3/uptime/config.go | Public configuration + normalization/validation for service and endpoint probe settings. |
| v3/uptime/README.md | Package documentation, usage examples, config reference, and behavior notes. |
| v3/uptime/internal/storage/types.go | Storage interface and shared data types for heartbeats and rollups. |
| v3/uptime/internal/storage/redis.go | Redis store implementation for services, samples, daily rollups, and cleanup. |
| v3/uptime/internal/storage/redis_test.go | Redis store tests including rollup/cleanup lifecycle and concurrency max-last-seen logic. |
| v3/uptime/go.mod | New module for the uptime package and its dependencies. |
| v3/uptime/go.sum | Dependency checksums for the uptime module. |
| .github/workflows/test-uptime.yml | CI workflow to run uptime module tests (incl. race). |
| v3/README.md | Adds uptime to the v3 middleware listing. |
| README.md | Adds uptime to the repository’s main middleware listing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
gaby
left a comment
There was a problem hiding this comment.
Automated high-effort code review of the new uptime middleware. Candidates were gathered across 8 independent review angles and each surviving finding was independently verified against the code (and fiber v3.3.0 / fasthttp sources where framework behavior mattered); several were reproduced empirically with throwaway tests. 10 findings, most severe first:
- Shutdown panic/race —
RedisStore.Close()nils the client while in-flight handlers can still call it; Fiber runsOnPreShutdownbefore draining requests (redis.go:369). - Multi-instance data loss — concurrent rollup/cleanup across instances can permanently zero a correctly-finalized day (
redis.go:169). - Group mounting silently breaks —
c.Path()includes the group prefix, soapp.Group("/admin").Use(uptime.New(...))makes the dashboard 404 (uptime.go:274). - Startup fragility —
New()panics on transient Redis errors (ping/upserts/initial heartbeat/maintenance) even though a degraded mode exists (uptime.go:74). - Outage masking — the failed-probe path clears
lastErrwithout any successful storage operation, flippingstorage: "degraded"back to"ok"during a Redis outage (uptime.go:388). - False red bars — today's expected slots include the in-progress slot while the heartbeat ticker isn't slot-aligned; healthy services render red/yellow for ~50 min after every midnight with defaults (
status.go:169). - 3xx probes impossible — the probe
http.Clientfollows redirects, soExpectedStatusCodes: []int{301}can never match (uptime.go:130). - Unstoppable runtime — without optional
Config.Appthere is no close path; goroutines leak and a stopped app keeps reporting "up" (uptime.go:231). - Redis key leak — instance hashes are written every boot with no TTL, never read, never cleaned (
redis.go:116). - CI gap — the workflow paths filter misses the
go:embed-eddashboard.gohtml(test-uptime.yml:9).
Notable items that didn't make the top-10 cut: HEAD responses lose the entity Content-Length (fasthttp would suppress the body automatically if the handlers just wrote it); QueryDaily/QueryTodaySamples each re-call ListServices so one snapshot fetches the service list three times; the persisted uptime_rate field is never read back (status.go recomputes it) and rate() duplicates uptimeRate(); explicit GreenThreshold/YellowThreshold of 0 is silently replaced by defaults despite docs saying the range is [0, 1]; and the README example registers store.Close before the runtime's shutdown hook, so final heartbeats hit a closed client.
Generated by Claude Code
| targets := buildRecordTargets(cfg, now, instanceID) | ||
| for _, target := range targets { | ||
| if err := store.UpsertService(ctx, target.service); err != nil { | ||
| _ = store.Close() | ||
| return nil, fmt.Errorf("uptime: upsert service: %w", err) | ||
| } | ||
| if err := store.UpsertInstance(ctx, target.instance); err != nil { | ||
| _ = store.Close() | ||
| return nil, fmt.Errorf("uptime: upsert instance: %w", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
@gofurry we should be close to merging after this fix
There was a problem hiding this comment.
Target registration now goes through the existing startup path. This avoids using the Redis client before Init() while preserving degraded startup behavior when Redis is temporarily unavailable.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

This PR adds a new
uptimemiddleware for Fiber v3.The middleware records in-process heartbeat history, stores uptime state through
github.com/gofiber/storage/redis/v3, and serves a built-in status dashboard with a JSON status API.Summary by CodeRabbit
/api/statusJSON endpoint plus an auto-refreshing interactive dashboard with live/stale/error indicators.