package main // The first breadcrumbs, written before anything else can fail. // // OpsLog keeps its log in the data folder, which lives beside the executable — // so every fault that happens BEFORE that folder exists is invisible. Reported // from a Windows 10 machine: "the process appears, no data folder is created, // nothing starts", with no file anywhere to say why. There was nothing to read // because the only place we write to had not been created yet. // // This writes to %LOCALAPPDATA%\OpsLog\startup.log instead: a folder Windows // guarantees is writable for the user, whatever OpsLog itself was installed // into. It records the handful of milestones between the process starting and // the window appearing, and nothing else — it is not a second log, it is the // answer to "it does not start". import ( "fmt" "os" "path/filepath" "strings" "time" ) // bootLogPath is the file, or "" when even LOCALAPPDATA is unavailable. func bootLogPath() string { dir := os.Getenv("LOCALAPPDATA") if strings.TrimSpace(dir) == "" { dir = os.TempDir() } if dir == "" { return "" } dir = filepath.Join(dir, "OpsLog") if err := os.MkdirAll(dir, 0o755); err != nil { return "" } return filepath.Join(dir, "startup.log") } // bootLog appends one line. Never fails loudly: it exists to explain a failure, // so it must not become one. func bootLog(format string, args ...any) { p := bootLogPath() if p == "" { return } f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { return } defer f.Close() // Trimmed when it gets long. A startup log that grows for two years is a // file nobody opens, and the interesting launch is always the last one. if fi, err := f.Stat(); err == nil && fi.Size() > 256*1024 { f.Close() _ = os.Remove(p) f, err = os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { return } defer f.Close() } fmt.Fprintf(f, "%s %s\n", time.Now().Format("2006-01-02 15:04:05.000"), fmt.Sprintf(format, args...)) } // bootLogLaunch records what was launched and from where. func bootLogLaunch() { exe, _ := os.Executable() bootLog("launch: %s %v", exe, os.Args[1:]) } // checkDataDirWritable makes sure the folder OpsLog keeps everything in can // actually be created and written to, and says exactly what failed if not. // // The data folder sits beside the executable, which is fine on a stick or in a // home directory and refused outright under Program Files — where Windows // silently denies the write to anything not elevated. That refusal used to end // the launch with no window and no message. func checkDataDirWritable() error { dir, err := userDataDir() if err != nil { return fmt.Errorf("cannot work out where to keep the data: %w", err) } if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("cannot create the data folder %s: %w", dir, err) } probe := filepath.Join(dir, ".writetest") if err := os.WriteFile(probe, []byte("ok"), 0o644); err != nil { return fmt.Errorf("the data folder %s cannot be written to: %w", dir, err) } _ = os.Remove(probe) bootLog("data dir ok: %s", dir) return nil }