package logging import ( "errors" "fmt" "os" "os/exec" "syscall" "golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/svc/eventlog" "r00t2.io/sysutils/paths" ) /* Setup sets up/configures a WinLogger and prepares it for use. This will fail with an Access Denied (the first time, at least) unless running with elevated permissions unless WinLogger.Prefix is a registered Event Log source. If a failure occurs while trying to open the log with the given WinLogger.Prefix ("source"), a new Event Log source will be registered. If WinLogger.Executable is not empty at the time of calling WinLogger.Setup (or WinLogger.ForceService is true), eventlog.Install will be used (with the WinLogger.ExpandKey field). Otherwise eventlog.InstallAsEventCreate will be used. */ func (l *WinLogger) Setup() (err error) { /* A sanity check on the EventIDs. Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000. */ for _, eid := range []uint32{ l.EIDs.Alert, l.EIDs.Crit, l.EIDs.Debug, l.EIDs.Emerg, l.EIDs.Err, l.EIDs.Info, l.EIDs.Notice, l.EIDs.Warning, } { if !((eid <= EIDMax) && (EIDMin <= eid)) { err = ErrBadEid return } } if err = l.Install(); err != nil { return } if l.elog, err = eventlog.Open(l.Prefix); err != nil { return } return } // Install installs/registers the WinLogger Event Log interface. You most likely do not need to run this directly. func (l *WinLogger) Install() (err error) { var exists bool var doNotCreate bool var useEventCreate bool = true if doNotCreate, err = l.Exists(); err != nil { return } else if !doNotCreate { if l.Executable != "" { if l.Executable, err = exec.LookPath(l.Executable); err != nil { return } useEventCreate = false } else if l.ForceService { if l.Executable, err = exec.LookPath(os.Args[0]); err != nil { return } useEventCreate = false } if !useEventCreate { if exists, err = paths.RealPathExists(&l.Executable); err != nil { return } else if !exists { err = ErrBadBinPath return } if err = eventlog.Install(l.Prefix, l.Executable, l.ExpandKey, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { return } } else { if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { return } } } return } // Remove uninstalls a registered WinLogger source. func (l *WinLogger) Remove() (err error) { if err = eventlog.Remove(l.Prefix); err != nil { return } return } /* Shutdown cleanly shuts down a WinLogger but keep the source registered. Use WinLogger.Remove (or set WinLogger.RemoveOnClose to true before calling WinLogger.Shutdown) to remove the registered source. */ func (l *WinLogger) Shutdown() (err error) { if err = l.elog.Close(); err != nil { // TODO: check for no access or file not exists syscall errors? return } if l.RemoveOnClose { if err = l.Remove(); err != nil { return } } return } /* GetPrefix returns the prefix used by this WinLogger. err will always be nil; it's there for interface-compat. */ func (l *WinLogger) GetPrefix() (prefix string, err error) { prefix = l.Prefix return } /* DoDebug sets the debug state of this WinLogger. Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. err will always be nil; it's there for interface-compat. */ func (l *WinLogger) DoDebug(d bool) (err error) { l.EnableDebug = d return } // GetDebug returns the debug status of this WinLogger. func (l *WinLogger) GetDebug() (d bool) { d = l.EnableDebug return } // SetPrefix sets the prefix for this WinLogger. func (l *WinLogger) SetPrefix(prefix string) (err error) { // To properly change the prefix, we need to tear down the old event log and create a new one. if err = l.Shutdown(); err != nil { return } l.Prefix = prefix if err = l.Setup(); err != nil { return } return } // Exists indicates if the WinLogger.Prefix is a registered source or not. func (l *WinLogger) Exists() (e bool, err error) { var regKey registry.Key var subKey registry.Key if regKey, err = registry.OpenKey(registry.LOCAL_MACHINE, eventLogRegistryKey, registry.READ); err != nil { return } defer regKey.Close() if subKey, err = registry.OpenKey(regKey, l.Prefix, registry.READ); err != nil { if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) { e = false err = nil } return } defer subKey.Close() e = true return } // Alert writes an ALERT-level message to this WinLogger. func (l *WinLogger) Alert(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } // Treat ALERT as Warning err = l.elog.Warning(l.EIDs.Alert, msg) return } // Crit writes an CRITICAL-level message to this WinLogger. func (l *WinLogger) Crit(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } // Treat CRIT as Error err = l.elog.Error(l.EIDs.Crit, msg) return } // Debug writes a DEBUG-level message to this WinLogger. func (l *WinLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { return } var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } // Treat DEBUG as Info err = l.elog.Info(l.EIDs.Debug, msg) return } // Emerg writes an EMERGENCY-level message to this WinLogger. func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } // Treat EMERG as Error err = l.elog.Error(l.EIDs.Emerg, msg) return } // Err writes an ERROR-level message to this WinLogger. func (l *WinLogger) Err(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } err = l.elog.Error(l.EIDs.Err, msg) return } // Info writes an INFO-level message to this WinLogger. func (l *WinLogger) Info(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } err = l.elog.Info(l.EIDs.Info, msg) return } // Notice writes a NOTICE-level message to this WinLogger. func (l *WinLogger) Notice(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } // Treat NOTICE as Info err = l.elog.Info(l.EIDs.Notice, msg) return } // Warning writes a WARNING/WARN-level message to this WinLogger. func (l *WinLogger) Warning(s string, v ...interface{}) (err error) { var msg string if v != nil { msg = fmt.Sprintf(s, v...) } else { msg = s } err = l.elog.Warning(l.EIDs.Warning, msg) return }