Add optional "log_file" configuration option.

This commit is contained in:
Kalzu Rekku
2026-07-18 00:14:49 +03:00
parent 216074b1b1
commit 2de4d378ca
3 changed files with 19 additions and 12 deletions
+4 -2
View File
@@ -17,6 +17,9 @@ type Config struct {
// DataDir is the base directory for all database files. // DataDir is the base directory for all database files.
DataDir string `json:"data_dir"` 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 is how many hours of raw ticks to keep in the hot DB.
HotRetentionHours int `json:"hot_retention_hours"` HotRetentionHours int `json:"hot_retention_hours"`
@@ -42,6 +45,7 @@ func DefaultConfig() Config {
WebSocketURL: "wss://stream.bybit.com/v5/public/linear", WebSocketURL: "wss://stream.bybit.com/v5/public/linear",
Symbol: "BTCUSDT", Symbol: "BTCUSDT",
DataDir: "data", DataDir: "data",
LogFile: "engine.log",
HotRetentionHours: 12, HotRetentionHours: 12,
FeatureRetentionDays: 30, FeatureRetentionDays: 30,
WriterFlushIntervalMs: 500, WriterFlushIntervalMs: 500,
@@ -52,14 +56,12 @@ func DefaultConfig() Config {
} }
// LoadConfig reads a JSON config file and merges with defaults. // 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) { func LoadConfig(path string) (Config, error) {
cfg := DefaultConfig() cfg := DefaultConfig()
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// Write default config for user reference
if writeErr := writeDefaultConfig(path, cfg); writeErr != nil { if writeErr := writeDefaultConfig(path, cfg); writeErr != nil {
return cfg, fmt.Errorf("failed to write default config: %w", writeErr) return cfg, fmt.Errorf("failed to write default config: %w", writeErr)
} }
+2 -1
View File
@@ -2,10 +2,11 @@
"websocket_url": "wss://stream.bybit.com/v5/public/linear", "websocket_url": "wss://stream.bybit.com/v5/public/linear",
"symbol": "BTCUSDT", "symbol": "BTCUSDT",
"data_dir": "data", "data_dir": "data",
"log_file": "engine.log",
"hot_retention_hours": 12, "hot_retention_hours": 12,
"feature_retention_days": 30, "feature_retention_days": 30,
"writer_flush_interval_ms": 500, "writer_flush_interval_ms": 500,
"writer_batch_size": 100, "writer_batch_size": 100,
"tick_channel_buffer": 10000, "tick_channel_buffer": 10000,
"maintenance_interval_minutes": 60 "maintenance_interval_minutes": 60
} }
+13 -9
View File
@@ -40,7 +40,7 @@ func main() {
} }
if isDaemon { if isDaemon {
startDaemon() startDaemon(cfg.LogFile)
return return
} }
runEngine(cfg) runEngine(cfg)
@@ -78,13 +78,17 @@ func printUsage() {
fmt.Println("Usage: engine [command] [--daemon]") fmt.Println("Usage: engine [command] [--daemon]")
fmt.Println("\nCommands:") fmt.Println("\nCommands:")
fmt.Println(" run Start the WebSocket ingestor and processing engine") 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(" recover Run startup recovery to migrate stale ticks")
fmt.Println(" maintain Run a single maintenance cycle (cleanup/retention)") fmt.Println(" maintain Run a single maintenance cycle (cleanup/retention)")
fmt.Println(" help Show this help message") 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) // Prepare arguments for the child process (remove --daemon)
args := []string{} args := []string{}
for _, arg := range os.Args[1:] { 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...) cmd := exec.Command(os.Args[0], args...)
// Open log file for output redirection // Open the configured log file for output redirection
logFile, err := os.OpenFile("engine.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil { 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.Stdout = logFile
cmd.Stderr = logFile cmd.Stderr = logFile
// Start the process // Start the process in the background
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Fatalf("Failed to start daemon: %v", err) 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) os.Exit(0)
} }