service: update api.

This commit is contained in:
Daniel Theophanes
2015-01-13 21:40:07 -08:00
parent 071b50c909
commit bc48b26bdb
7 changed files with 134 additions and 84 deletions
BIN
View File
Binary file not shown.
+28 -13
View File
@@ -10,30 +10,31 @@ import (
var logger service.Logger var logger service.Logger
// Program structures.
// Define Start and Stop methods.
type program struct { type program struct {
exit chan struct{} exit chan struct{}
} }
func (p *program) Start(s service.Service) error { func (p *program) Start(s service.Service) error {
if s.Interactive() { if service.Local.Interactive() {
logger.Info("Running in terminal.") logger.Info("Running in terminal.")
} else { } else {
logger.Info("Running under service manager.") logger.Info("Running under service manager.")
} }
p.exit = make(chan struct{}) p.exit = make(chan struct{})
// Start should not block. Do the actual work async.
go p.run() go p.run()
return nil return nil
} }
func (p *program) run() error { 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) ticker := time.NewTicker(2 * time.Second)
for { for {
select { select {
case tm := <-ticker.C: case tm := <-ticker.C:
err := logger.Infof("Still running at %v...", tm) logger.Infof("Still running at %v...", tm)
if err != nil {
panic(err)
}
case <-p.exit: case <-p.exit:
ticker.Stop() ticker.Stop()
return nil return nil
@@ -42,14 +43,18 @@ func (p *program) run() error {
return nil return nil
} }
func (p *program) Stop(s service.Service) error { func (p *program) Stop(s service.Service) error {
err := logger.Info("I'm Stopping!") // Any work in Stop should be quick, usually a few seconds at most.
if err != nil { logger.Info("I'm Stopping!")
panic(err)
}
close(p.exit) close(p.exit)
return nil return nil
} }
// Service setup.
// Define service config.
// Create the service.
// Setup the logger.
// Handle service controls (optional).
// Run the service.
func main() { func main() {
svcConfig := &service.Config{ svcConfig := &service.Config{
Name: "GoServiceTest", Name: "GoServiceTest",
@@ -60,16 +65,26 @@ func main() {
prg := &program{} prg := &program{}
s, err := service.New(prg, svcConfig) s, err := service.New(prg, svcConfig)
if err != nil { 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 { if err != nil {
panic(err) log.Fatal(err)
} }
go func() {
for {
err := <-errs
if err != nil {
log.Print(err)
}
}
}()
if len(os.Args) > 1 { if len(os.Args) > 1 {
err := service.Control(s, os.Args[1]) err := service.Control(s, os.Args[1])
if err != nil { if err != nil {
log.Printf("Valid actions: %q\n", service.ControlAction)
log.Fatal(err) log.Fatal(err)
} }
return return
+11 -11
View File
@@ -87,12 +87,14 @@ func (kv KeyValue) float64(name string, defaultValue float64) float64 {
type System interface { type System interface {
// String returns a description of the OS and service platform. // String returns a description of the OS and service platform.
String() string String() string
// Interactive returns false if running under the OS service manager
// and true otherwise.
Interactive() bool
} }
// LocalSystem get's the local system information. // LocalSystem get's the local system information.
func LocalSystem() System { var Local System = system
return system
}
// Interface represents the service interface for a program. Start runs before // Interface represents the service interface for a program. Start runs before
// the hosting process is granted control and Stop runs when control is returned. // 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. // greater rights. Will return an error if the service is not present.
Remove() error 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 // Opens and returns a system logger. If the user program is running
// interactively rather then as a service, the returned logger will write to // interactively rather then as a service, the returned logger will write to
// os.Stderr. // os.Stderr. If errs is non-nil errors will be sent on errs as well as
Logger() (Logger, error) // returned from Logger's functions.
Logger(errs chan<- error) (Logger, error)
// SystemLogger opens and returns a system logger. // SystemLogger opens and returns a system logger. If errs is non-nil errors
SystemLogger() (Logger, error) // 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, // String displays the name of the service. The display name if present,
// otherwise the name. // otherwise the name.
+20 -14
View File
@@ -22,8 +22,22 @@ func (ls darwinSystem) String() string {
return version return version
} }
func (ls darwinSystem) Interactive() bool {
return interactive
}
var system = darwinSystem{} var system = darwinSystem{}
var interactive = false
func init() {
var err error
interactive, err = isInteractive()
if err != nil {
panic(err)
}
}
func isInteractive() (bool, error) { func isInteractive() (bool, error) {
// TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID. // TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID.
return os.Getppid() != 1, nil return os.Getppid() != 1, nil
@@ -35,17 +49,12 @@ func newService(i Interface, c *Config) (*darwinLaunchdService, error) {
Config: c, Config: c,
} }
var err error return s, nil
s.interactive, err = isInteractive()
return s, err
} }
type darwinLaunchdService struct { type darwinLaunchdService struct {
i Interface i Interface
*Config *Config
interactive bool
} }
func (s *darwinLaunchdService) String() string { func (s *darwinLaunchdService) String() string {
@@ -66,9 +75,6 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
} }
func (s *darwinLaunchdService) Interactive() bool {
return s.interactive
}
func (s *darwinLaunchdService) Install() error { func (s *darwinLaunchdService) Install() error {
confPath, err := s.getServiceFilePath() confPath, err := s.getServiceFilePath()
if err != nil { if err != nil {
@@ -166,14 +172,14 @@ func (s *darwinLaunchdService) Run() error {
return s.i.Stop(s) return s.i.Stop(s)
} }
func (s *darwinLaunchdService) Logger() (Logger, error) { func (s *darwinLaunchdService) Logger(errs chan<- error) (Logger, error) {
if s.interactive { if interactive {
return ConsoleLogger, nil return ConsoleLogger, nil
} }
return s.SystemLogger() return s.SystemLogger(errs)
} }
func (s *darwinLaunchdService) SystemLogger() (Logger, error) { func (s *darwinLaunchdService) SystemLogger(errs chan<- error) (Logger, error) {
return newSysLogger(s.Name) return newSysLogger(s.Name, errs)
} }
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?> var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
+18 -8
View File
@@ -56,6 +56,10 @@ func (ls linuxSystem) String() string {
return fmt.Sprintf("Linux %s", flavor.String()) return fmt.Sprintf("Linux %s", flavor.String())
} }
func (ls linuxSystem) Interactive() bool {
return interactive
}
var system = linuxSystem{} var system = linuxSystem{}
func newService(i Interface, c *Config) (Service, error) { 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)) 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) { func isInteractive() (bool, error) {
// TODO: Is this true for user services? // TODO: Is this true for user services?
return os.Getppid() != 1, nil return os.Getppid() != 1, nil
} }
func (s *linuxService) Interactive() bool {
return s.interactive
}
func (s *linuxService) Install() error { func (s *linuxService) Install() error {
confPath := flavor.ConfigPath(s.Name) confPath := flavor.ConfigPath(s.Name)
_, err := os.Stat(confPath) _, err := os.Stat(confPath)
@@ -192,14 +202,14 @@ func (s *linuxService) Remove() error {
return nil return nil
} }
func (s *linuxService) Logger() (Logger, error) { func (s *linuxService) Logger(errs chan<- error) (Logger, error) {
if s.interactive { if s.interactive {
return ConsoleLogger, nil return ConsoleLogger, nil
} }
return s.SystemLogger() return s.SystemLogger(errs)
} }
func (s *linuxService) SystemLogger() (Logger, error) { func (s *linuxService) SystemLogger(errs chan<- error) (Logger, error) {
return newSysLogger(s.Name) return newSysLogger(s.Name, errs)
} }
func (s *linuxService) Run() (err error) { func (s *linuxService) Run() (err error) {
+16 -8
View File
@@ -9,33 +9,41 @@ import (
"log/syslog" "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) w, err := syslog.New(syslog.LOG_INFO, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return sysLogger{w}, nil return sysLogger{w, errs}, nil
} }
type sysLogger struct { type sysLogger struct {
*syslog.Writer *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 { 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 { 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 { 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 { 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 { 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 { 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...)))
} }
+41 -30
View File
@@ -17,13 +17,12 @@ const version = "Windows Service"
type windowsService struct { type windowsService struct {
i Interface i Interface
*Config *Config
interactive bool
} }
// WindowsLogger allows using windows specific logging methods. // WindowsLogger allows using windows specific logging methods.
type WindowsLogger struct { type WindowsLogger struct {
ev *eventlog.Log ev *eventlog.Log
errs chan<- error
} }
type windowsSystem struct{} type windowsSystem struct{}
@@ -31,49 +30,67 @@ type windowsSystem struct{}
func (windowsSystem) String() string { func (windowsSystem) String() string {
return version return version
} }
func (windowsSystem) Interactive() bool {
return interactive
}
var system = windowsSystem{} 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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) { var interactive = false
return svc.IsAnInteractiveSession()
func init() {
var err error
interactive, err = svc.IsAnInteractiveSession()
if err != nil {
panic(err)
}
} }
func newService(i Interface, c *Config) (*windowsService, error) { func newService(i Interface, c *Config) (*windowsService, error) {
@@ -81,9 +98,7 @@ func newService(i Interface, c *Config) (*windowsService, error) {
i: i, i: i,
Config: c, Config: c,
} }
var err error return ws, nil
ws.interactive, err = isInteractive()
return ws, err
} }
func (ws *windowsService) String() string { func (ws *windowsService) String() string {
@@ -126,10 +141,6 @@ loop:
return false, 0 return false, 0
} }
func (ws *windowsService) Interactive() bool {
return ws.interactive
}
func (ws *windowsService) Install() error { func (ws *windowsService) Install() error {
exepath, err := osext.Executable() exepath, err := osext.Executable()
if err != nil { if err != nil {
@@ -187,7 +198,7 @@ func (ws *windowsService) Remove() error {
} }
func (ws *windowsService) Run() error { func (ws *windowsService) Run() error {
if !ws.interactive { if !interactive {
return svc.Run(ws.Name, ws) return svc.Run(ws.Name, ws)
} }
err := ws.i.Start(ws) err := ws.i.Start(ws)
@@ -243,16 +254,16 @@ func (ws *windowsService) Restart() error {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
return ws.Start() return ws.Start()
} }
func (ws *windowsService) Logger() (Logger, error) { func (ws *windowsService) Logger(errs chan<- error) (Logger, error) {
if ws.interactive { if interactive {
return ConsoleLogger, nil 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) el, err := eventlog.Open(ws.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return WindowsLogger{el}, nil return WindowsLogger{el, errs}, nil
} }