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
+71 -36
View File
@@ -5,45 +5,80 @@ This module provides an entry point that works with PyInstaller by using
absolute imports instead of relative imports.
"""
import argparse
import uvicorn
import sys
import logging
# Import the FastAPI app from the backend package
from backend.main import app
from backend import config, database
# Set up logging FIRST, before any imports that might fail
logging.basicConfig(
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__":
parser = argparse.ArgumentParser(description="voicebox backend server")
parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="Host to bind to (use 0.0.0.0 for remote access)",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port to bind to",
)
parser.add_argument(
"--data-dir",
type=str,
default=None,
help="Data directory for database, profiles, and generated audio",
)
args = parser.parse_args()
try:
parser = argparse.ArgumentParser(description="voicebox backend server")
parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="Host to bind to (use 0.0.0.0 for remote access)",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port to bind to",
)
parser.add_argument(
"--data-dir",
type=str,
default=None,
help="Data directory for database, profiles, and generated audio",
)
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
if args.data_dir:
config.set_data_dir(args.data_dir)
# Set data directory if provided
if args.data_dir:
logger.info(f"Setting data directory to: {args.data_dir}")
config.set_data_dir(args.data_dir)
# Initialize database after data directory is set
database.init_db()
# Initialize database after data directory is set
logger.info("Initializing database...")
database.init_db()
logger.info("Database initialized successfully")
uvicorn.run(
app,
host=args.host,
port=args.port,
log_level="info",
)
logger.info(f"Starting uvicorn server on {args.host}:{args.port}...")
uvicorn.run(
app,
host=args.host,
port=args.port,
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)
.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
.shell()
.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
sidecar = sidecar.args([
@@ -48,9 +59,21 @@ async fn start_server(
sidecar = sidecar.args(["--host", "0.0.0.0"]);
}
println!("Spawning server process...");
let (mut rx, child) = sidecar
.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
*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
let timeout = tokio::time::Duration::from_secs(30);
let start_time = tokio::time::Instant::now();
let mut error_output = Vec::new();
loop {
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 {
@@ -77,9 +108,14 @@ async fn start_server(
}
}
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);
// 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
if line_str.contains("Uvicorn running") || line_str.contains("Application startup complete") {
println!("Server is ready!");
@@ -90,6 +126,9 @@ async fn start_server(
}
}
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());
}
Err(_) => {
+2 -1
View File
@@ -24,7 +24,8 @@
"macOS": {
"frameworks": [],
"minimumSystemVersion": "11.0",
"infoPlist": "Info.plist"
"infoPlist": "Info.plist",
"entitlements": "Entitlements.plist"
},
"resources": {
"gen/Assets.car": "./",