diff --git a/orbit/changes/issue-5689-decouple-orbit-logs b/orbit/changes/issue-5689-decouple-orbit-logs new file mode 100644 index 0000000000..2e5dda1de0 --- /dev/null +++ b/orbit/changes/issue-5689-decouple-orbit-logs @@ -0,0 +1,4 @@ +* Added log files for Fleet Desktop logs, located at: + - macOS: `~/Library/Log` + - Linux: `$XDG_STATE_HOME`, fallback to `$HOME/.local/state` + - Windows: `%LocalAppData%` diff --git a/orbit/cmd/desktop/desktop.go b/orbit/cmd/desktop/desktop.go index c3da3a231f..2444c7546d 100644 --- a/orbit/cmd/desktop/desktop.go +++ b/orbit/cmd/desktop/desktop.go @@ -4,44 +4,48 @@ import ( _ "embed" "errors" "fmt" - "log" "net/url" "os" "path" + "path/filepath" + "runtime" "time" "github.com/fleetdm/fleet/v4/pkg/open" "github.com/fleetdm/fleet/v4/server/service" "github.com/getlantern/systray" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "gopkg.in/natefinch/lumberjack.v2" ) // version is set at compile time via -ldflags var version = "unknown" func main() { + setupLogs() + // Our TUF provided targets must support launching with "--help". if len(os.Args) > 1 && os.Args[1] == "--help" { fmt.Println("Fleet Desktop application executable") return } - log.Printf("fleet-desktop version=%s\n", version) + log.Info().Msgf("fleet-desktop version=%s", version) devURL := os.Getenv("FLEET_DESKTOP_DEVICE_URL") if devURL == "" { - log.Println("missing URL environment FLEET_DESKTOP_DEVICE_URL") - os.Exit(1) + log.Fatal().Msg("missing URL environment FLEET_DESKTOP_DEVICE_URL") } deviceURL, err := url.Parse(devURL) if err != nil { - log.Printf("invalid URL argument: %s\n", err) - os.Exit(1) + log.Fatal().Err(err).Msg("invalid URL argument") } basePath := deviceURL.Scheme + "://" + deviceURL.Host deviceToken := path.Base(deviceURL.Path) onReady := func() { - log.Println("ready") + log.Info().Msg("ready") systray.SetTemplateIcon(icoBytes, icoBytes) systray.SetTooltip("Fleet Device Management Menu.") @@ -64,8 +68,7 @@ func main() { client, err := service.NewDeviceClient(basePath, deviceToken, insecureSkipVerify, "") if err != nil { - log.Printf("unable to initialize request client: %s", err) - os.Exit(1) + log.Fatal().Err(err).Msg("unable to initialize request client") } // Perform API test call to enable the "My device" item as soon @@ -90,7 +93,7 @@ func main() { // To ease troubleshooting we set the tooltip as the error. myDeviceItem.SetTooltip(err.Error()) - log.Printf("get device URL: %s", err) + log.Error().Err(err).Msg("get device URL") <-ticker.C } @@ -117,7 +120,7 @@ func main() { default: // To ease troubleshooting we set the tooltip as the error. myDeviceItem.SetTooltip(err.Error()) - log.Printf("get device URL: %s", err) + log.Error().Err(err).Msg("get device URL") continue } @@ -139,19 +142,94 @@ func main() { select { case <-myDeviceItem.ClickedCh: if err := open.Browser(deviceURL.String()); err != nil { - log.Printf("open browser my device: %s", err) + log.Error().Err(err).Msg("open browser my device") } case <-transparencyItem.ClickedCh: if err := open.Browser("https://fleetdm.com/transparency"); err != nil { - log.Printf("open browser transparency: %s", err) + log.Error().Err(err).Msg("open browser transparency") } } } }() } onExit := func() { - log.Println("exit") + log.Info().Msg("exit") } systray.Run(onReady, onExit) } + +// setupLogs configures our logging system to write logs to rolling files and +// stderr, if for some reason we can't write a log file the logs are still +// printed to stderr. +func setupLogs() { + stderrOut := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true} + + dir, err := logDir() + if err != nil { + log.Logger = log.Output(stderrOut) + log.Error().Err(err).Msg("find directory for logs") + return + } + + dir = filepath.Join(dir, "Fleet") + + if err := os.MkdirAll(dir, 0755); err != nil { + log.Logger = log.Output(stderrOut) + log.Error().Err(err).Msg("make directories for log files") + return + } + + logFile := &lumberjack.Logger{ + Filename: filepath.Join(dir, "fleet-desktop.log"), + MaxSize: 25, // megabytes + MaxBackups: 3, + MaxAge: 28, // days + } + + log.Logger = log.Output(zerolog.MultiLevelWriter( + zerolog.ConsoleWriter{Out: logFile, TimeFormat: time.RFC3339Nano, NoColor: true}, + stderrOut, + )) +} + +// logDir returns the default root directory to use for application-level logs. +// +// On Unix systems, it returns $XDG_STATE_HOME as specified by +// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if +// non-empty, else $HOME/.local/state. +// On Darwin, it returns $HOME/Library/Log. +// On Windows, it returns %LocalAppData% +// +// If the location cannot be determined (for example, $HOME is not defined), +// then it will return an error. +func logDir() (string, error) { + var dir string + + switch runtime.GOOS { + case "windows": + dir = os.Getenv("LocalAppData") + if dir == "" { + return "", errors.New("%LocalAppData% is not defined") + } + + case "darwin": + dir = os.Getenv("HOME") + if dir == "" { + return "", errors.New("$HOME is not defined") + } + dir += "/Library/Log" + + default: // Unix + dir = os.Getenv("XDG_STATE_HOME") + if dir == "" { + dir = os.Getenv("HOME") + if dir == "" { + return "", errors.New("neither $XDG_STATE_HOME nor $HOME are defined") + } + dir += "/.local/state" + } + } + + return dir, nil +}