diff --git a/example/example b/example/example deleted file mode 100755 index 7ef42d0..0000000 Binary files a/example/example and /dev/null differ diff --git a/example/main.go b/example/main.go index 424125c..637f997 100644 --- a/example/main.go +++ b/example/main.go @@ -10,30 +10,31 @@ import ( var logger service.Logger +// Program structures. +// Define Start and Stop methods. type program struct { exit chan struct{} } func (p *program) Start(s service.Service) error { - if s.Interactive() { + if service.Local.Interactive() { logger.Info("Running in terminal.") } else { logger.Info("Running under service manager.") } p.exit = make(chan struct{}) + + // Start should not block. Do the actual work async. go p.run() return nil } func (p *program) run() error { - logger.Infof("I'm running %v.", service.LocalSystem()) + logger.Infof("I'm running %v.", service.Local) ticker := time.NewTicker(2 * time.Second) for { select { case tm := <-ticker.C: - err := logger.Infof("Still running at %v...", tm) - if err != nil { - panic(err) - } + logger.Infof("Still running at %v...", tm) case <-p.exit: ticker.Stop() return nil @@ -42,14 +43,18 @@ func (p *program) run() error { return nil } func (p *program) Stop(s service.Service) error { - err := logger.Info("I'm Stopping!") - if err != nil { - panic(err) - } + // Any work in Stop should be quick, usually a few seconds at most. + logger.Info("I'm Stopping!") close(p.exit) return nil } +// Service setup. +// Define service config. +// Create the service. +// Setup the logger. +// Handle service controls (optional). +// Run the service. func main() { svcConfig := &service.Config{ Name: "GoServiceTest", @@ -60,16 +65,26 @@ func main() { prg := &program{} s, err := service.New(prg, svcConfig) if err != nil { - panic(err) + log.Fatal(err) } - logger, err = s.Logger() + errs := make(chan error, 5) + logger, err = s.Logger(errs) if err != nil { - panic(err) + log.Fatal(err) } + go func() { + for { + err := <-errs + if err != nil { + log.Print(err) + } + } + }() if len(os.Args) > 1 { err := service.Control(s, os.Args[1]) if err != nil { + log.Printf("Valid actions: %q\n", service.ControlAction) log.Fatal(err) } return diff --git a/service.go b/service.go index d6d346a..1d3c4a7 100644 --- a/service.go +++ b/service.go @@ -87,12 +87,14 @@ func (kv KeyValue) float64(name string, defaultValue float64) float64 { type System interface { // String returns a description of the OS and service platform. String() string + + // Interactive returns false if running under the OS service manager + // and true otherwise. + Interactive() bool } // LocalSystem get's the local system information. -func LocalSystem() System { - return system -} +var Local System = system // Interface represents the service interface for a program. Start runs before // the hosting process is granted control and Stop runs when control is returned. @@ -143,17 +145,15 @@ type Service interface { // greater rights. Will return an error if the service is not present. Remove() error - // Interactive returns false if running under the OS service manager - // and true otherwise. - Interactive() bool - // Opens and returns a system logger. If the user program is running // interactively rather then as a service, the returned logger will write to - // os.Stderr. - Logger() (Logger, error) + // os.Stderr. If errs is non-nil errors will be sent on errs as well as + // returned from Logger's functions. + Logger(errs chan<- error) (Logger, error) - // SystemLogger opens and returns a system logger. - SystemLogger() (Logger, error) + // SystemLogger opens and returns a system logger. If errs is non-nil errors + // will be sent on errs as well as returned from Logger's functions. + SystemLogger(errs chan<- error) (Logger, error) // String displays the name of the service. The display name if present, // otherwise the name. diff --git a/service_darwin.go b/service_darwin.go index 64efa17..4c805ff 100644 --- a/service_darwin.go +++ b/service_darwin.go @@ -22,8 +22,22 @@ func (ls darwinSystem) String() string { return version } +func (ls darwinSystem) Interactive() bool { + return interactive +} + var system = darwinSystem{} +var interactive = false + +func init() { + var err error + interactive, err = isInteractive() + if err != nil { + panic(err) + } +} + func isInteractive() (bool, error) { // TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID. return os.Getppid() != 1, nil @@ -35,17 +49,12 @@ func newService(i Interface, c *Config) (*darwinLaunchdService, error) { Config: c, } - var err error - s.interactive, err = isInteractive() - - return s, err + return s, nil } type darwinLaunchdService struct { i Interface *Config - - interactive bool } func (s *darwinLaunchdService) String() string { @@ -66,9 +75,6 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) { return "/Library/LaunchDaemons/" + s.Name + ".plist", nil } -func (s *darwinLaunchdService) Interactive() bool { - return s.interactive -} func (s *darwinLaunchdService) Install() error { confPath, err := s.getServiceFilePath() if err != nil { @@ -166,14 +172,14 @@ func (s *darwinLaunchdService) Run() error { return s.i.Stop(s) } -func (s *darwinLaunchdService) Logger() (Logger, error) { - if s.interactive { +func (s *darwinLaunchdService) Logger(errs chan<- error) (Logger, error) { + if interactive { return ConsoleLogger, nil } - return s.SystemLogger() + return s.SystemLogger(errs) } -func (s *darwinLaunchdService) SystemLogger() (Logger, error) { - return newSysLogger(s.Name) +func (s *darwinLaunchdService) SystemLogger(errs chan<- error) (Logger, error) { + return newSysLogger(s.Name, errs) } var launchdConfig = ` diff --git a/service_linux.go b/service_linux.go index ad7cf72..a731bf8 100644 --- a/service_linux.go +++ b/service_linux.go @@ -56,6 +56,10 @@ func (ls linuxSystem) String() string { return fmt.Sprintf("Linux %s", flavor.String()) } +func (ls linuxSystem) Interactive() bool { + return interactive +} + var system = linuxSystem{} func newService(i Interface, c *Config) (Service, error) { @@ -117,15 +121,21 @@ func (f initFlavor) GetTemplate() *template.Template { return template.Must(template.New(f.String() + "Script").Parse(templ)) } +var interactive = false + +func init() { + var err error + interactive, err = isInteractive() + if err != nil { + panic(err) + } +} + func isInteractive() (bool, error) { // TODO: Is this true for user services? return os.Getppid() != 1, nil } -func (s *linuxService) Interactive() bool { - return s.interactive -} - func (s *linuxService) Install() error { confPath := flavor.ConfigPath(s.Name) _, err := os.Stat(confPath) @@ -192,14 +202,14 @@ func (s *linuxService) Remove() error { return nil } -func (s *linuxService) Logger() (Logger, error) { +func (s *linuxService) Logger(errs chan<- error) (Logger, error) { if s.interactive { return ConsoleLogger, nil } - return s.SystemLogger() + return s.SystemLogger(errs) } -func (s *linuxService) SystemLogger() (Logger, error) { - return newSysLogger(s.Name) +func (s *linuxService) SystemLogger(errs chan<- error) (Logger, error) { + return newSysLogger(s.Name, errs) } func (s *linuxService) Run() (err error) { diff --git a/service_unix.go b/service_unix.go index c906ac4..d6c56b8 100644 --- a/service_unix.go +++ b/service_unix.go @@ -9,33 +9,41 @@ import ( "log/syslog" ) -func newSysLogger(name string) (Logger, error) { +func newSysLogger(name string, errs chan<- error) (Logger, error) { w, err := syslog.New(syslog.LOG_INFO, name) if err != nil { return nil, err } - return sysLogger{w}, nil + return sysLogger{w, errs}, nil } type sysLogger struct { *syslog.Writer + errs chan<- error +} + +func (s sysLogger) send(err error) error { + if err != nil && s.errs != nil { + s.errs <- err + } + return err } func (s sysLogger) Error(v ...interface{}) error { - return s.Writer.Err(fmt.Sprint(v...)) + return s.send(s.Writer.Err(fmt.Sprint(v...))) } func (s sysLogger) Warning(v ...interface{}) error { - return s.Writer.Warning(fmt.Sprint(v...)) + return s.send(s.Writer.Warning(fmt.Sprint(v...))) } func (s sysLogger) Info(v ...interface{}) error { - return s.Writer.Info(fmt.Sprint(v...)) + return s.send(s.Writer.Info(fmt.Sprint(v...))) } func (s sysLogger) Errorf(format string, a ...interface{}) error { - return s.Writer.Err(fmt.Sprintf(format, a...)) + return s.send(s.Writer.Err(fmt.Sprintf(format, a...))) } func (s sysLogger) Warningf(format string, a ...interface{}) error { - return s.Writer.Warning(fmt.Sprintf(format, a...)) + return s.send(s.Writer.Warning(fmt.Sprintf(format, a...))) } func (s sysLogger) Infof(format string, a ...interface{}) error { - return s.Writer.Info(fmt.Sprintf(format, a...)) + return s.send(s.Writer.Info(fmt.Sprintf(format, a...))) } diff --git a/service_windows.go b/service_windows.go index 7aea57d..9698ae7 100644 --- a/service_windows.go +++ b/service_windows.go @@ -17,13 +17,12 @@ const version = "Windows Service" type windowsService struct { i Interface *Config - - interactive bool } // WindowsLogger allows using windows specific logging methods. type WindowsLogger struct { - ev *eventlog.Log + ev *eventlog.Log + errs chan<- error } type windowsSystem struct{} @@ -31,49 +30,67 @@ type windowsSystem struct{} func (windowsSystem) String() string { return version } +func (windowsSystem) Interactive() bool { + return interactive +} var system = windowsSystem{} +func (l WindowsLogger) send(err error) error { + if err == nil { + return nil + } + if l.errs != nil { + l.errs <- err + } + return err +} func (l WindowsLogger) Error(v ...interface{}) error { - return l.ev.Error(3, fmt.Sprint(v...)) + return l.send(l.ev.Error(3, fmt.Sprint(v...))) } func (l WindowsLogger) Warning(v ...interface{}) error { - return l.ev.Warning(2, fmt.Sprint(v...)) + return l.send(l.ev.Warning(2, fmt.Sprint(v...))) } func (l WindowsLogger) Info(v ...interface{}) error { - return l.ev.Info(1, fmt.Sprint(v...)) + return l.send(l.ev.Info(1, fmt.Sprint(v...))) } func (l WindowsLogger) Errorf(format string, a ...interface{}) error { - return l.ev.Error(3, fmt.Sprintf(format, a...)) + return l.send(l.ev.Error(3, fmt.Sprintf(format, a...))) } func (l WindowsLogger) Warningf(format string, a ...interface{}) error { - return l.ev.Warning(2, fmt.Sprintf(format, a...)) + return l.send(l.ev.Warning(2, fmt.Sprintf(format, a...))) } func (l WindowsLogger) Infof(format string, a ...interface{}) error { - return l.ev.Info(1, fmt.Sprintf(format, a...)) + return l.send(l.ev.Info(1, fmt.Sprintf(format, a...))) } func (l WindowsLogger) NError(eventId uint32, v ...interface{}) error { - return l.ev.Error(eventId, fmt.Sprint(v...)) + return l.send(l.ev.Error(eventId, fmt.Sprint(v...))) } func (l WindowsLogger) NWarning(eventId uint32, v ...interface{}) error { - return l.ev.Warning(eventId, fmt.Sprint(v...)) + return l.send(l.ev.Warning(eventId, fmt.Sprint(v...))) } func (l WindowsLogger) NInfo(eventId uint32, v ...interface{}) error { - return l.ev.Info(eventId, fmt.Sprint(v...)) + return l.send(l.ev.Info(eventId, fmt.Sprint(v...))) } func (l WindowsLogger) NErrorf(eventId uint32, format string, a ...interface{}) error { - return l.ev.Error(eventId, fmt.Sprintf(format, a...)) + return l.send(l.ev.Error(eventId, fmt.Sprintf(format, a...))) } func (l WindowsLogger) NWarningf(eventId uint32, format string, a ...interface{}) error { - return l.ev.Warning(eventId, fmt.Sprintf(format, a...)) + return l.send(l.ev.Warning(eventId, fmt.Sprintf(format, a...))) } func (l WindowsLogger) NInfof(eventId uint32, format string, a ...interface{}) error { - return l.ev.Info(eventId, fmt.Sprintf(format, a...)) + return l.send(l.ev.Info(eventId, fmt.Sprintf(format, a...))) } -func isInteractive() (bool, error) { - return svc.IsAnInteractiveSession() +var interactive = false + +func init() { + var err error + interactive, err = svc.IsAnInteractiveSession() + if err != nil { + panic(err) + } } func newService(i Interface, c *Config) (*windowsService, error) { @@ -81,9 +98,7 @@ func newService(i Interface, c *Config) (*windowsService, error) { i: i, Config: c, } - var err error - ws.interactive, err = isInteractive() - return ws, err + return ws, nil } func (ws *windowsService) String() string { @@ -126,10 +141,6 @@ loop: return false, 0 } -func (ws *windowsService) Interactive() bool { - return ws.interactive -} - func (ws *windowsService) Install() error { exepath, err := osext.Executable() if err != nil { @@ -187,7 +198,7 @@ func (ws *windowsService) Remove() error { } func (ws *windowsService) Run() error { - if !ws.interactive { + if !interactive { return svc.Run(ws.Name, ws) } err := ws.i.Start(ws) @@ -243,16 +254,16 @@ func (ws *windowsService) Restart() error { time.Sleep(50 * time.Millisecond) return ws.Start() } -func (ws *windowsService) Logger() (Logger, error) { - if ws.interactive { +func (ws *windowsService) Logger(errs chan<- error) (Logger, error) { + if interactive { return ConsoleLogger, nil } - return ws.SystemLogger() + return ws.SystemLogger(errs) } -func (ws *windowsService) SystemLogger() (Logger, error) { +func (ws *windowsService) SystemLogger(errs chan<- error) (Logger, error) { el, err := eventlog.Open(ws.Name) if err != nil { return nil, err } - return WindowsLogger{el}, nil + return WindowsLogger{el, errs}, nil }