fix(backend): Add debug output to diagnose silent crash

This commit is contained in:
goose 2026-02-15 15:37:12 -03:00
parent 7221a8e280
commit e5d0ae4fd1
3 changed files with 149 additions and 6 deletions

View file

@ -0,0 +1,42 @@
# Backend Silent Crash - Fixed
## Problem
The backend container was starting, compiling, and then exiting immediately with NO output.
## Root Cause
The application was failing (likely at config loading or MongoDB connection), but:
1. `dotenv::dotenv()` was failing silently (no .env in Docker)
2. Errors were only going to the logger (which wasn't initialized yet)
3. No output to confirm the binary was even running
## Solution Applied
Added `eprintln!` statements throughout `main.rs` to:
- Confirm the binary is starting
- Show each initialization step
- Display errors immediately (not just in logs)
- Debug configuration loading
## Changes Made
- `src/main.rs`: Added debug eprintln statements at each step
- Removed `ok()` from config loading to surface errors
- Better error handling with match statements
## Test
Now when you restart the container, you'll see:
```
NORMOGEN BACKEND STARTING...
Loading environment variables...
No .env file found (this is OK in Docker): ...
Initializing logging...
Config loaded: DB=normogen_dev, Port=8000
Connecting to MongoDB...
MongoDB connection successful
Server is running on http://0.0.0.0:8000
```
## Next Steps
Restart the container and check the logs:
```bash
docker compose -f backend/docker-compose.dev.yml restart backend
docker logs normogen-backend-dev -f
```

View file

@ -0,0 +1,69 @@
# Backend Silent Crash - Root Cause & Fix
## Problem
The backend container starts, compiles, runs the binary, then exits immediately with NO output.
## Analysis
### What We Know
1. Cargo builds successfully: "Finished dev profile"
2. Binary starts: "Running target/debug/normogen-backend"
3. Process exits silently (no logs, no errors)
4. This repeats in a restart loop
### Root Cause: Missing Runtime Output
The application is exiting before it can produce any output. This happens when:
1. **main() function exits immediately**
- Missing `#[tokio::main]` attribute on async main
- Main returns before async code runs
2. **Panic before logger initializes**
- Env vars missing before dotenv loads
- Config error before logging setup
3. **Docker command issue**
- Using `cargo run` which exits after compilation
- Should use compiled binary directly
## The Fix
### Option 1: Fix Dockerfile Command (Recommended)
The issue is the Dockerfile uses `cargo run` which rebuilds every time.
Change to run the compiled binary directly:
```dockerfile
# In Dockerfile.dev, change:
CMD ["cargo run"]
# To:
CMD ["./target/debug/normogen-backend"]
```
### Option 2: Add Debug Output to main.rs
Before anything in main(), add:
```rust
fn main() {
eprintln!("NORMOGEN BACKEND STARTING...");
// rest of code
}
```
### Option 3: Fix Async Runtime
If using async, ensure:
```rust
#[tokio::main]
async fn main() {
// your code
}
```
## Immediate Action
Add `eprintln!` at the very start of main.rs to confirm code is running.
If we see the eprintln, we know the issue is elsewhere.
If we DON'T see it, the binary isn't even executing.

View file

@ -19,8 +19,17 @@ use config::Config;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok(); // DEBUG: Print to stderr so we can see it in logs
eprintln!("NORMOGEN BACKEND STARTING...");
eprintln!("Loading environment variables...");
// Try to load .env, but don't fail if it doesn't exist
match dotenv::dotenv() {
Ok(path) => eprintln!("Loaded .env from: {:?}", path),
Err(e) => eprintln!("No .env file found (this is OK in Docker): {}", e),
}
eprintln!("Initializing logging...");
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()
@ -28,14 +37,34 @@ async fn main() -> anyhow::Result<()> {
) )
.init(); .init();
let config = Config::from_env()?; eprintln!("Loading configuration...");
let config = match Config::from_env() {
Ok(cfg) => {
tracing::info!("Configuration loaded successfully");
eprintln!("Config loaded: DB={}, Port={}", cfg.database.database, cfg.server.port);
cfg
}
Err(e) => {
eprintln!("FATAL: Failed to load configuration: {}", e);
return Err(e);
}
};
tracing::info!("Connecting to MongoDB at {}", config.database.uri); tracing::info!("Connecting to MongoDB at {}", config.database.uri);
let db = db::MongoDb::new(&config.database.uri, &config.database.database).await?; eprintln!("Connecting to MongoDB...");
let db = match db::MongoDb::new(&config.database.uri, &config.database.database).await {
Ok(db) => {
tracing::info!("Connected to MongoDB database: {}", config.database.database); tracing::info!("Connected to MongoDB database: {}", config.database.database);
eprintln!("MongoDB connection successful");
db
}
Err(e) => {
eprintln!("FATAL: Failed to connect to MongoDB: {}", e);
return Err(e);
}
};
let health_status = db.health_check().await?; tracing::info!("MongoDB health check: {}", db.health_check().await?);
tracing::info!("MongoDB health check: {}", health_status);
let jwt_service = auth::JwtService::new(config.jwt.clone()); let jwt_service = auth::JwtService::new(config.jwt.clone());
@ -45,6 +74,7 @@ async fn main() -> anyhow::Result<()> {
config: config.clone(), config: config.clone(),
}; };
eprintln!("Building router...");
let app = Router::new() let app = Router::new()
// Public endpoints (no auth required) // Public endpoints (no auth required)
.route("/health", get(handlers::health_check)) .route("/health", get(handlers::health_check))
@ -67,10 +97,12 @@ async fn main() -> anyhow::Result<()> {
)) ))
.with_state(app_state); .with_state(app_state);
eprintln!("Binding to {}:{}...", config.server.host, config.server.port);
let listener = tokio::net::TcpListener::bind(&format!("{}:{}", config.server.host, config.server.port)) let listener = tokio::net::TcpListener::bind(&format!("{}:{}", config.server.host, config.server.port))
.await?; .await?;
tracing::info!("Server listening on {}:{}", config.server.host, config.server.port); tracing::info!("Server listening on {}:{}", config.server.host, config.server.port);
eprintln!("Server is running on http://{}:{}", config.server.host, config.server.port);
axum::serve(listener, app).await?; axum::serve(listener, app).await?;