Skip to content

feat: add-heathcheck-database#48

Merged
RambokDev merged 3 commits intomainfrom
feat/add-heathcheck-database
Mar 28, 2026
Merged

feat: add-heathcheck-database#48
RambokDev merged 3 commits intomainfrom
feat/add-heathcheck-database

Conversation

@RambokDev
Copy link
Copy Markdown
Contributor

@RambokDev RambokDev commented Mar 28, 2026

Summary by CodeRabbit

  • New Features

    • Database ping status is now included in API status responses, providing per-database connectivity insights.
    • Concurrent database reachability checks for improved performance and efficiency.
  • Chores

    • Connection pooling configuration now validates values with safety warnings for suboptimal settings.
    • Enhanced diagnostic logging for connection initialization and authentication events.
    • Updated Docker Compose environment variable configurations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

The changes implement health-checking enhancements for database connections and service status endpoints. Database ping operations are refactored to execute concurrently, logging levels are adjusted across Redis and Valkey implementations, and the status API payload now includes ping status information. Configuration validation is added for connection pooling parameters.

Changes

Cohort / File(s) Summary
Docker Composition Configuration
docker-compose.prod.yml, docker-compose.yml
EDGE_KEY updated from hardcoded token to placeholder; POOLING environment variable activated with value 900; minor comment formatting adjustments.
Settings Validation
src/settings.rs
Added validation logic for POOLING environment variable to panic if outside 1–600 second range and emit warnings for values below 5 seconds.
Database Ping Domain Logic
src/domain/redis/ping.rs, src/domain/valkey/ping.rs
Logging adjustments across both implementations: log levels elevated from info to error for non-success cases and NOAUTH failures; debug messages updated with clearer command labels; stderr logging now conditional on non-empty stdout.
Status Service & API Endpoint
src/services/status.rs, src/services/api/endpoints/agent/status.rs
Ping method refactored to execute per-database ping operations concurrently using try_join_all; DatabasePayload struct augmented with ping_status: bool field serialized as pingStatus.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant StatusService
    participant DatabaseFactory
    participant DBEngine1
    participant DBEngine2
    participant APIEndpoint

    Client->>StatusService: request ping status
    StatusService->>StatusService: collect database configs
    par Concurrent Execution
        StatusService->>DatabaseFactory: create_for_backup(config1)
        DatabaseFactory->>DBEngine1: new instance
        StatusService->>DatabaseFactory: create_for_backup(config2)
        DatabaseFactory->>DBEngine2: new instance
    and
        DBEngine1->>DBEngine1: ping()
        DBEngine2->>DBEngine2: ping()
    end
    DBEngine1-->>StatusService: reachable status
    DBEngine2-->>StatusService: reachable status
    StatusService->>StatusService: construct DatabasePayload with ping_status
    StatusService->>APIEndpoint: post status request
    APIEndpoint-->>Client: JSON response with pingStatus
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰✨ A hop through the code, so swift and so fine,
Concurrent pings dancing in parallel time,
Each database whispers its heartbeat alive,
While pooling finds balance to help systems thrive,
Health checks now sing in the service's refrain!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'add-heathcheck-database' contains a typo ('heathcheck' instead of 'healthcheck') and is somewhat vague; however, it does relate to the core change of adding database health/ping checks across multiple files. Consider revising the title to 'feat: add database healthcheck functionality' to fix the typo and make the scope clearer while maintaining conciseness.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-heathcheck-database

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 28, 2026

Codecov Report

❌ Patch coverage is 20.58824% with 27 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/status.rs 0.00% 11 Missing ⚠️
src/settings.rs 0.00% 9 Missing ⚠️
src/domain/valkey/ping.rs 50.00% 4 Missing ⚠️
src/domain/redis/ping.rs 50.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
docker-compose.prod.yml (1)

15-15: Prefer operator-supplied EDGE_KEY over a literal placeholder.

Line 15 still injects a known-invalid value into the container, and src/core/context.rs will then panic during startup when it tries to decode it. If this file is meant to be a template, requiring a real runtime value gives a clearer failure mode than shipping a baked-in bad key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.prod.yml` at line 15, Replace the literal placeholder for
EDGE_KEY in docker-compose (the line setting EDGE_KEY: "<to complete>") with an
operator-supplied environment reference so the runtime provides a real key (e.g.
use a compose variable like ${EDGE_KEY} or the stricter ${EDGE_KEY?} to fail at
startup if missing); this avoids injecting a known-invalid value that causes
src/core/context.rs to panic when decoding the key. Ensure the service's
environment uses that variable reference rather than a hardcoded string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yml`:
- Line 24: The commented-out POOLING sample in docker-compose.yml uses 900 which
is now rejected by the settings validator (see the POOLING validation in
src/settings.rs), so either remove the commented line or update the value to an
in-range number (<=600); specifically edit the POOLING sample in
docker-compose.yml to a value like 600 (or any <=600) or delete the comment to
prevent startup failure due to validation in the function that parses/validates
POOLING.

In `@src/domain/redis/ping.rs`:
- Around line 34-36: The log condition is checking the wrong stream: change the
gate from stdout to stderr so that error!("Redis stderr: {}", stderr) only runs
when stderr is non-empty; locate the block using the stdout and stderr variables
(in the Redis ping handling function) and replace if !stdout.is_empty() { ... }
with if !stderr.is_empty() { error!("Redis stderr: {}", stderr); }.
- Line 24: The debug log prints the full tokio::process::Command (variable cmd)
which includes Redis credentials added with "-a"; instead, sanitize before
logging by constructing a redacted representation (e.g., clone or extract
cmd.get_args()/arg list, replace the password value after "-a" with "<REDACTED>"
or remove that flag+value) and log that sanitized string or args rather than the
raw cmd; update the debug call in ping.rs (the debug!("Command Ping Redis:
{:?}", cmd) site) to use the redacted representation.

In `@src/domain/valkey/ping.rs`:
- Around line 34-36: The log condition is checking the wrong stream: instead of
gating the stderr log on stdout, change the condition to check stderr (i.e., if
!stderr.is_empty()) so that error!("Valkey stderr: {}", stderr) runs only when
stderr contains data; locate this in the ping logic where stdout and stderr
variables are inspected (variables stdout, stderr in src/domain/valkey/ping.rs)
and update the conditional accordingly.
- Line 24: The debug statement prints the full Command (cmd) including arguments
and thus exposes the Valkey password added with "-a"; change the logging to
avoid {:?} on cmd and instead log only the executable and a sanitized args list:
inspect cmd's arguments (the variable cmd in ping.rs / the function building the
Command), mask or omit the value that follows "-a" (replace with "<redacted>" or
drop that arg), and log the filtered/masked args or just the program name so
credentials are not emitted.

In `@src/services/status.rs`:
- Around line 30-43: Replace the fallible try_join_all with join_all and make
the per-database async closure swallow ping errors: call
DatabaseFactory::create_for_backup and db_engine.ping() inside the closure, then
on Err(log the error with your crate logger, e.g. tracing::error!/log::error!)
and return DatabasePayload with ping_status: false (preserving name, dbms,
generated_id), and on Ok set ping_status to true/returned value; also update the
import to use join_all instead of try_join_all so a single ping failure does not
abort the whole aggregation.

---

Nitpick comments:
In `@docker-compose.prod.yml`:
- Line 15: Replace the literal placeholder for EDGE_KEY in docker-compose (the
line setting EDGE_KEY: "<to complete>") with an operator-supplied environment
reference so the runtime provides a real key (e.g. use a compose variable like
${EDGE_KEY} or the stricter ${EDGE_KEY?} to fail at startup if missing); this
avoids injecting a known-invalid value that causes src/core/context.rs to panic
when decoding the key. Ensure the service's environment uses that variable
reference rather than a hardcoded string.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1c17f54-0610-4e02-91f9-13ee88fc22bb

📥 Commits

Reviewing files that changed from the base of the PR and between a1e1875 and 3d5a7ee.

📒 Files selected for processing (7)
  • docker-compose.prod.yml
  • docker-compose.yml
  • src/domain/redis/ping.rs
  • src/domain/valkey/ping.rs
  • src/services/api/endpoints/agent/status.rs
  • src/services/status.rs
  • src/settings.rs

cmd.arg("PING");

debug!("Command Ping: {:?}", cmd);
debug!("Command Ping Redis: {:?}", cmd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/domain/redis/ping.rs

Repository: Portabase/agent

Length of output: 2009


Redact credentials from the debug command log.

Line 24 logs the full tokio::process::Command after line 19 appends the password with the -a flag. Since tokio::process::Command's Debug output includes all arguments, this will expose Redis credentials in debug logs.

🔐 Safer logging
-    debug!("Command Ping Redis: {:?}", cmd);
+    debug!("Running Redis ping against {}:{}", cfg.host, cfg.port);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debug!("Command Ping Redis: {:?}", cmd);
debug!("Running Redis ping against {}:{}", cfg.host, cfg.port);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/redis/ping.rs` at line 24, The debug log prints the full
tokio::process::Command (variable cmd) which includes Redis credentials added
with "-a"; instead, sanitize before logging by constructing a redacted
representation (e.g., clone or extract cmd.get_args()/arg list, replace the
password value after "-a" with "<REDACTED>" or remove that flag+value) and log
that sanitized string or args rather than the raw cmd; update the debug call in
ping.rs (the debug!("Command Ping Redis: {:?}", cmd) site) to use the redacted
representation.

Comment on lines +34 to +36
if !stdout.is_empty() {
error!("Redis stderr: {}", stderr);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The stderr log is gated by the wrong stream.

Line 34 checks stdout, so a healthy PONG path will emit Redis stderr: on success, while stderr-only failures can be skipped. This condition should be keyed off stderr.

🩹 Suggested fix
-            if !stdout.is_empty() {
+            if !stderr.is_empty() {
                 error!("Redis stderr: {}", stderr);
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !stdout.is_empty() {
error!("Redis stderr: {}", stderr);
}
if !stderr.is_empty() {
error!("Redis stderr: {}", stderr);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/redis/ping.rs` around lines 34 - 36, The log condition is checking
the wrong stream: change the gate from stdout to stderr so that error!("Redis
stderr: {}", stderr) only runs when stderr is non-empty; locate the block using
the stdout and stderr variables (in the Redis ping handling function) and
replace if !stdout.is_empty() { ... } with if !stderr.is_empty() { error!("Redis
stderr: {}", stderr); }.

cmd.arg("PING");

debug!("Command Ping: {:?}", cmd);
debug!("Command Ping Valkey: {:?}", cmd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "ping.rs" -path "*/valkey/*" | head -5

Repository: Portabase/agent

Length of output: 86


🏁 Script executed:

cat -n ./src/domain/valkey/ping.rs

Repository: Portabase/agent

Length of output: 2016


🏁 Script executed:

find . -name "Cargo.toml" -type f | head -1 | xargs cat | grep -A 20 "dependencies"

Repository: Portabase/agent

Length of output: 1125


🌐 Web query:

tokio::process::Command Debug implementation 1.49 arguments

💡 Result:

The tokio::process::Command struct implements the Debug trait. This has been present at least since early versions like 1.8.0 and continues in the latest versions (1.50.0 as of 2026-03-03). In Tokio 1.49.0, a fix was made to "remove unnecessary trait bounds on the Debug implementation" for tasks, but the Command Debug impl itself is standard and derives from its fields, including the underlying std::process::Command (which shows program and arguments) and the kill_on_drop flag. The Debug output typically displays the configured program name, arguments, environment variables, working directory, and other settings from the wrapped std::process::Command, plus Tokio-specific fields like kill_on_drop.

Citations:


Redact credentials from the debug command log.

Line 24 logs the full command using {:?} format after Line 19 appends the password with the -a flag. The Debug implementation of tokio::process::Command includes all configured arguments, which will expose Valkey credentials in debug logs.

🔐 Safer logging
-    debug!("Command Ping Valkey: {:?}", cmd);
+    debug!("Running Valkey ping against {}:{}", cfg.host, cfg.port);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debug!("Command Ping Valkey: {:?}", cmd);
debug!("Running Valkey ping against {}:{}", cfg.host, cfg.port);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/valkey/ping.rs` at line 24, The debug statement prints the full
Command (cmd) including arguments and thus exposes the Valkey password added
with "-a"; change the logging to avoid {:?} on cmd and instead log only the
executable and a sanitized args list: inspect cmd's arguments (the variable cmd
in ping.rs / the function building the Command), mask or omit the value that
follows "-a" (replace with "<redacted>" or drop that arg), and log the
filtered/masked args or just the program name so credentials are not emitted.

Comment on lines +34 to +36
if !stdout.is_empty() {
error!("Valkey stderr: {}", stderr);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The stderr log is gated by the wrong stream.

Line 34 checks stdout, so a healthy PONG path will emit Valkey stderr: on success, while stderr-only failures can be skipped. This condition should be keyed off stderr.

🩹 Suggested fix
-            if !stdout.is_empty() {
+            if !stderr.is_empty() {
                 error!("Valkey stderr: {}", stderr);
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !stdout.is_empty() {
error!("Valkey stderr: {}", stderr);
}
if !stderr.is_empty() {
error!("Valkey stderr: {}", stderr);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/valkey/ping.rs` around lines 34 - 36, The log condition is
checking the wrong stream: instead of gating the stderr log on stdout, change
the condition to check stderr (i.e., if !stderr.is_empty()) so that
error!("Valkey stderr: {}", stderr) runs only when stderr contains data; locate
this in the ping logic where stdout and stderr variables are inspected
(variables stdout, stderr in src/domain/valkey/ping.rs) and update the
conditional accordingly.

Comment on lines +30 to +43
let databases_payload: Vec<DatabasePayload> = try_join_all(
databases.into_iter().map(|db| async move {
let db_engine = DatabaseFactory::create_for_backup(db.clone()).await;

let reachable = db_engine.ping().await?;

Ok::<DatabasePayload, anyhow::Error>(DatabasePayload {
name: &db.name,
dbms: &db.db_type.as_str(),
generated_id: &db.generated_id,
ping_status: reachable,
})
})
.collect();
).await?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the status.rs file
find . -name "status.rs" -path "*/services/*" | head -5

Repository: Portabase/agent

Length of output: 229


🏁 Script executed:

# Look for ping.rs files in domain subdirectories
find . -name "ping.rs" -path "*/domain/*" | head -10

Repository: Portabase/agent

Length of output: 227


🏁 Script executed:

# Check the repository structure
ls -la src/ 2>/dev/null | head -20

Repository: Portabase/agent

Length of output: 620


🏁 Script executed:

# Read the status.rs file to see lines 30-43 and the import at line 11
cat -n src/services/status.rs | head -50

Repository: Portabase/agent

Length of output: 1973


🏁 Script executed:

# Read redis/ping.rs around line 30
cat -n src/domain/redis/ping.rs | head -40

Repository: Portabase/agent

Length of output: 1528


🏁 Script executed:

# Read valkey/ping.rs around line 30
cat -n src/domain/valkey/ping.rs | head -40

Repository: Portabase/agent

Length of output: 1533


🏁 Script executed:

# Check a couple other ping implementations to see if pattern is consistent
cat -n src/domain/mysql/ping.rs | head -40

Repository: Portabase/agent

Length of output: 955


🏁 Script executed:

# Check how the ping result is used in the context
cat -n src/services/status.rs | tail -20

Repository: Portabase/agent

Length of output: 767


🏁 Script executed:

# Search for callers of the StatusService::ping method
rg "StatusService::ping|\.ping\(" src/services/ -A 3 -B 1

Repository: Portabase/agent

Length of output: 897


One ping error should not abort the entire status report.

Line 30–43 uses try_join_all(...).await?, so any Err from a single database aborts the entire payload. This happens because src/domain/redis/ping.rs Line 30 and src/domain/valkey/ping.rs Line 30 bubble CLI execution errors with ?. However, src/domain/mysql/ping.rs already handles errors gracefully by returning Ok(false). For consistency and better observability in a health endpoint, apply the same pattern: log ping failures per database and emit ping_status: false rather than failing the whole status report.

Other services in this codebase (backup/runner.rs, restore/runner.rs) already use graceful degradation with match or unwrap_or(false), so this change aligns with existing patterns.

🩹 Suggested approach
-        let databases_payload: Vec<DatabasePayload> = try_join_all(
+        let databases_payload: Vec<DatabasePayload> = join_all(
             databases.into_iter().map(|db| async move {
                 let db_engine = DatabaseFactory::create_for_backup(db.clone()).await;
-
-                let reachable = db_engine.ping().await?;
-
-                Ok::<DatabasePayload, anyhow::Error>(DatabasePayload {
+                let reachable = match db_engine.ping().await {
+                    Ok(reachable) => reachable,
+                    Err(err) => {
+                        tracing::error!(database = %db.name, error = ?err, "Database ping failed");
+                        false
+                    }
+                };
+
+                DatabasePayload {
                     name: &db.name,
                     dbms: &db.db_type.as_str(),
                     generated_id: &db.generated_id,
                     ping_status: reachable,
-                })
+                }
             })
-        ).await?;
+        ).await;

Update line 11 to import join_all instead of try_join_all.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/status.rs` around lines 30 - 43, Replace the fallible
try_join_all with join_all and make the per-database async closure swallow ping
errors: call DatabaseFactory::create_for_backup and db_engine.ping() inside the
closure, then on Err(log the error with your crate logger, e.g.
tracing::error!/log::error!) and return DatabasePayload with ping_status: false
(preserving name, dbms, generated_id), and on Ok set ping_status to
true/returned value; also update the import to use join_all instead of
try_join_all so a single ping failure does not abort the whole aggregation.

@RambokDev RambokDev merged commit fdf3522 into main Mar 28, 2026
2 checks passed
@RambokDev RambokDev deleted the feat/add-heathcheck-database branch March 28, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant