Enhance server startup logging and error handling

- Implemented detailed logging for server startup in server.py, including Python version, executable path, and parsed arguments.
- Added error handling for module imports and server initialization to improve robustness.
- Introduced an Entitlements.plist file for macOS to manage security settings.
- Updated tauri.conf.json to reference the new Entitlements.plist.
- Enhanced error reporting in main.rs for better debugging during server process management.
This commit is contained in:
Jamie Pine
2026-01-25 20:55:13 -08:00
parent 5feda1519c
commit af2f37ccb1
5 changed files with 128 additions and 41 deletions
+40 -5
View File
@@ -5,14 +5,41 @@ This module provides an entry point that works with PyInstaller by using
absolute imports instead of relative imports. absolute imports instead of relative imports.
""" """
import argparse import sys
import uvicorn import logging
# Import the FastAPI app from the backend package # Set up logging FIRST, before any imports that might fail
from backend.main import app logging.basicConfig(
from backend import config, database level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
stream=sys.stderr, # Log to stderr so it's captured by Tauri
)
logger = logging.getLogger(__name__)
# Log startup immediately to confirm binary execution
logger.info("=" * 60)
logger.info("voicebox-server starting up...")
logger.info(f"Python version: {sys.version}")
logger.info(f"Executable: {sys.executable}")
logger.info(f"Arguments: {sys.argv}")
logger.info("=" * 60)
try:
import argparse
import uvicorn
logger.info("Standard library imports successful")
# Import the FastAPI app from the backend package
logger.info("Importing backend modules...")
from backend.main import app
from backend import config, database
logger.info("Backend imports successful")
except Exception as e:
logger.error(f"Failed to import required modules: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="voicebox backend server") parser = argparse.ArgumentParser(description="voicebox backend server")
parser.add_argument( parser.add_argument(
"--host", "--host",
@@ -33,17 +60,25 @@ if __name__ == "__main__":
help="Data directory for database, profiles, and generated audio", help="Data directory for database, profiles, and generated audio",
) )
args = parser.parse_args() args = parser.parse_args()
logger.info(f"Parsed arguments: host={args.host}, port={args.port}, data_dir={args.data_dir}")
# Set data directory if provided # Set data directory if provided
if args.data_dir: if args.data_dir:
logger.info(f"Setting data directory to: {args.data_dir}")
config.set_data_dir(args.data_dir) config.set_data_dir(args.data_dir)
# Initialize database after data directory is set # Initialize database after data directory is set
logger.info("Initializing database...")
database.init_db() database.init_db()
logger.info("Database initialized successfully")
logger.info(f"Starting uvicorn server on {args.host}:{args.port}...")
uvicorn.run( uvicorn.run(
app, app,
host=args.host, host=args.host,
port=args.port, port=args.port,
log_level="info", log_level="info",
) )
except Exception as e:
logger.error(f"Server startup failed: {e}", exc_info=True)
sys.exit(1)
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
Binary file not shown.
+43 -4
View File
@@ -31,10 +31,21 @@ async fn start_server(
std::fs::create_dir_all(&data_dir) std::fs::create_dir_all(&data_dir)
.map_err(|e| format!("Failed to create data dir: {}", e))?; .map_err(|e| format!("Failed to create data dir: {}", e))?;
println!("=================================================================");
println!("Starting voicebox-server sidecar");
println!("Data directory: {:?}", data_dir);
println!("Remote mode: {}", remote.unwrap_or(false));
let mut sidecar = app let mut sidecar = app
.shell() .shell()
.sidecar("voicebox-server") .sidecar("voicebox-server")
.map_err(|e| format!("Failed to get sidecar: {}", e))?; .map_err(|e| {
eprintln!("Failed to get sidecar: {}", e);
eprintln!("This usually means the binary is not bundled correctly or doesn't have execute permissions");
format!("Failed to get sidecar: {}", e)
})?;
println!("Sidecar command created successfully");
// Pass data directory to Python server // Pass data directory to Python server
sidecar = sidecar.args([ sidecar = sidecar.args([
@@ -48,9 +59,21 @@ async fn start_server(
sidecar = sidecar.args(["--host", "0.0.0.0"]); sidecar = sidecar.args(["--host", "0.0.0.0"]);
} }
println!("Spawning server process...");
let (mut rx, child) = sidecar let (mut rx, child) = sidecar
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn: {}", e))?; .map_err(|e| {
eprintln!("Failed to spawn server process: {}", e);
eprintln!("This could be due to:");
eprintln!(" - Missing or corrupted binary");
eprintln!(" - Missing execute permissions");
eprintln!(" - Code signing issues on macOS");
eprintln!(" - Missing dependencies");
format!("Failed to spawn: {}", e)
})?;
println!("Server process spawned, waiting for ready signal...");
println!("=================================================================");
// Store child process // Store child process
*state.child.lock().unwrap() = Some(child); *state.child.lock().unwrap() = Some(child);
@@ -58,10 +81,18 @@ async fn start_server(
// Wait for server to be ready by listening for startup log // Wait for server to be ready by listening for startup log
let timeout = tokio::time::Duration::from_secs(30); let timeout = tokio::time::Duration::from_secs(30);
let start_time = tokio::time::Instant::now(); let start_time = tokio::time::Instant::now();
let mut error_output = Vec::new();
loop { loop {
if start_time.elapsed() > timeout { if start_time.elapsed() > timeout {
return Err("Server startup timeout".to_string()); eprintln!("Server startup timeout after 30 seconds");
if !error_output.is_empty() {
eprintln!("Collected error output:");
for line in &error_output {
eprintln!(" {}", line);
}
}
return Err("Server startup timeout - check Console.app for detailed logs".to_string());
} }
match tokio::time::timeout(tokio::time::Duration::from_millis(100), rx.recv()).await { match tokio::time::timeout(tokio::time::Duration::from_millis(100), rx.recv()).await {
@@ -77,9 +108,14 @@ async fn start_server(
} }
} }
tauri_plugin_shell::process::CommandEvent::Stderr(line) => { tauri_plugin_shell::process::CommandEvent::Stderr(line) => {
let line_str = String::from_utf8_lossy(&line); let line_str = String::from_utf8_lossy(&line).to_string();
eprintln!("Server: {}", line_str); eprintln!("Server: {}", line_str);
// Collect error lines for debugging
if line_str.contains("ERROR") || line_str.contains("Error") || line_str.contains("Failed") {
error_output.push(line_str.clone());
}
// Uvicorn logs to stderr, so check there too // Uvicorn logs to stderr, so check there too
if line_str.contains("Uvicorn running") || line_str.contains("Application startup complete") { if line_str.contains("Uvicorn running") || line_str.contains("Application startup complete") {
println!("Server is ready!"); println!("Server is ready!");
@@ -90,6 +126,9 @@ async fn start_server(
} }
} }
Ok(None) => { Ok(None) => {
eprintln!("Server process ended unexpectedly during startup!");
eprintln!("The server binary may have crashed or exited with an error.");
eprintln!("Check Console.app logs for more details (search for 'voicebox')");
return Err("Server process ended unexpectedly".to_string()); return Err("Server process ended unexpectedly".to_string());
} }
Err(_) => { Err(_) => {
+2 -1
View File
@@ -24,7 +24,8 @@
"macOS": { "macOS": {
"frameworks": [], "frameworks": [],
"minimumSystemVersion": "11.0", "minimumSystemVersion": "11.0",
"infoPlist": "Info.plist" "infoPlist": "Info.plist",
"entitlements": "Entitlements.plist"
}, },
"resources": { "resources": {
"gen/Assets.car": "./", "gen/Assets.car": "./",