Reported from a Windows 10 machine: the process appears, no data folder is created, nothing starts. There was nothing to read because the only log OpsLog has lives inside the folder that was never created. Three exits happen before any window or log exists, and all three were silent. Another instance already running — correct to refuse, since two would fight over the rig, but indistinguishable from a crash. The data folder unwritable — it is created BESIDE the executable, so a copy dropped into Program Files is refused by Windows outright. And wails.Run failing, which is where a missing WebView2 runtime lands; its error went to println, which in a GUI-subsystem program goes nowhere at all. Each now writes to %LOCALAPPDATA%\OpsLog\startup.log — a folder Windows guarantees the user can write to, whatever OpsLog was installed into — and shows a message box naming the fault and what to do about it.
96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
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
|
|
}
|