diff --git a/config.go b/config.go index 3be4eff..cc2cc76 100644 --- a/config.go +++ b/config.go @@ -17,6 +17,9 @@ type Config struct { // DataDir is the base directory for all database files. DataDir string `json:"data_dir"` + // LogFile is the optional path to a file where logs will be written. + LogFile string `json:"log_file"` + // HotRetentionHours is how many hours of raw ticks to keep in the hot DB. HotRetentionHours int `json:"hot_retention_hours"` @@ -42,6 +45,7 @@ func DefaultConfig() Config { WebSocketURL: "wss://stream.bybit.com/v5/public/linear", Symbol: "BTCUSDT", DataDir: "data", + LogFile: "engine.log", HotRetentionHours: 12, FeatureRetentionDays: 30, WriterFlushIntervalMs: 500, @@ -52,14 +56,12 @@ func DefaultConfig() Config { } // LoadConfig reads a JSON config file and merges with defaults. -// If the file does not exist, defaults are returned and a config file is written. func LoadConfig(path string) (Config, error) { cfg := DefaultConfig() data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { - // Write default config for user reference if writeErr := writeDefaultConfig(path, cfg); writeErr != nil { return cfg, fmt.Errorf("failed to write default config: %w", writeErr) } diff --git a/config.json b/config.json index 32ac297..aa8604d 100644 --- a/config.json +++ b/config.json @@ -2,10 +2,11 @@ "websocket_url": "wss://stream.bybit.com/v5/public/linear", "symbol": "BTCUSDT", "data_dir": "data", + "log_file": "engine.log", "hot_retention_hours": 12, "feature_retention_days": 30, "writer_flush_interval_ms": 500, "writer_batch_size": 100, "tick_channel_buffer": 10000, "maintenance_interval_minutes": 60 -} \ No newline at end of file +} diff --git a/main.go b/main.go index 9bf96b9..b725505 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func main() { } if isDaemon { - startDaemon() + startDaemon(cfg.LogFile) return } runEngine(cfg) @@ -78,13 +78,17 @@ func printUsage() { fmt.Println("Usage: engine [command] [--daemon]") fmt.Println("\nCommands:") fmt.Println(" run Start the WebSocket ingestor and processing engine") - fmt.Println(" Use --daemon to run in background and log to engine.log") + fmt.Println(" Use --daemon to run in background and log to the file defined in config") fmt.Println(" recover Run startup recovery to migrate stale ticks") fmt.Println(" maintain Run a single maintenance cycle (cleanup/retention)") fmt.Println(" help Show this help message") } -func startDaemon() { +func startDaemon(logPath string) { + if logPath == "" { + logPath = "engine.log" + } + // Prepare arguments for the child process (remove --daemon) args := []string{} for _, arg := range os.Args[1:] { @@ -93,24 +97,24 @@ func startDaemon() { } } - // Prepare the command + // Prepare the command to re-run the current binary cmd := exec.Command(os.Args[0], args...) - // Open log file for output redirection - logFile, err := os.OpenFile("engine.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + // Open the configured log file for output redirection + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - log.Fatalf("Failed to open log file: %v", err) + log.Fatalf("Failed to open log file %s: %v", logPath, err) } cmd.Stdout = logFile cmd.Stderr = logFile - // Start the process + // Start the process in the background if err := cmd.Start(); err != nil { log.Fatalf("Failed to start daemon: %v", err) } - fmt.Printf("Engine started in background (PID: %d). Logs: engine.log\n", cmd.Process.Pid) + fmt.Printf("Engine started in background (PID: %d). Logs: %s\n", cmd.Process.Pid, logPath) os.Exit(0) }