diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f980ab9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c541272 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.pyer.club/kingecg/godaemon + +go 1.19 + +require git.pyer.club/kingecg/gologger v1.0.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cfdf286 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.pyer.club/kingecg/gologger v1.0.1 h1:snCb0ePlfDUglX+CHwNzq5MRK5uNTnPUks1Dnapl/p8= +git.pyer.club/kingecg/gologger v1.0.1/go.mod h1:SNSl2jRHPzIpHSzdKOoVG798rtYMjPDPFyxUrEgivkY= diff --git a/godaemon.go b/godaemon.go new file mode 100644 index 0000000..5e8ef46 --- /dev/null +++ b/godaemon.go @@ -0,0 +1,188 @@ +package godaemon + +import ( + "flag" + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strconv" + "strings" + "syscall" + + "git.pyer.club/kingecg/gologger" +) + +const ( + daemon_env_key = "_go_daemon" + daemon_process = "g_daemon" + daemon_task = "g_dtask" + daemon_taskargs = "g_args" +) + +type GoDaemon struct { + pidFile string + flag *string + Running *exec.Cmd + sigChan chan os.Signal + doneChan chan int + state string + StartFn func(*GoDaemon) + StopFn func(*GoDaemon) +} + +func (g *GoDaemon) GetPid() int { + pids, ferr := os.ReadFile(g.pidFile) + pid, err := strconv.Atoi(string(pids)) + if err != nil || ferr != nil { + return 0 + } + return pid +} + +func (g *GoDaemon) Start() { + if g.flag == nil { + g.flag = flag.String("s", "", "send signal to daemon. support: reload and quit") + } + + if IsMaster() { + flag.Parse() + if *g.flag == "" { + g.startDaemon() + return + } + + var sig syscall.Signal + if *g.flag == "reload" { + sig = syscall.SIGHUP + } else if *g.flag == "quit" { + sig = syscall.SIGTERM + } else { + fmt.Println("Not supported signal") + return + } + + p := g.getDaemonProcess() + if p == nil { + fmt.Println("Daemon process not found") + return + } + fmt.Println("Send signal:", p.Pid, sig) + p.Signal(sig) + } else if IsDaemon() { + pid := os.Getpid() + + os.WriteFile(g.pidFile, []byte(strconv.Itoa(pid)), 0644) + g.sigChan = make(chan os.Signal) + g.doneChan = make(chan int) + signal.Notify(g.sigChan, syscall.SIGTERM, syscall.SIGHUP) + go g.serveSignal() + l := gologger.GetLogger("daemon") + + for { + if g.state == "running" { + l.Debug("Due to for loop ") + break + } + l.Debug("Starting task new") + g.Running = g.startTask() + g.state = "running" + g.Running.Process.Wait() + if g.state == "stopped" { + break + } + } + + } else { + + g.StartFn(g) + } +} + +func (g *GoDaemon) serveSignal() { + sig := <-g.sigChan + if sig == syscall.SIGTERM { + g.state = "stopped" + } else { + g.state = "restart" + } + l := gologger.GetLogger("daemon") + l.Debug("Stop it") + g.StopFn(g) +} + +func (g *GoDaemon) getDaemonProcess() *os.Process { + pid := g.GetPid() + if pid == 0 { + return nil + } + p, err := os.FindProcess(pid) + if err != nil { + l := gologger.GetLogger("default") + l.Debug(err) + } + serr := p.Signal(syscall.Signal(0)) + if serr != nil { + return nil + } + return p +} + +func (g *GoDaemon) startDaemon() { + dp := g.getDaemonProcess() + + if dp != nil { + fmt.Println("daemon is running with pid:", dp.Pid) + return + } + + execName, _ := os.Executable() + cmd := exec.Command(execName) + cmd.Env = append(cmd.Env, daemon_env_key+"="+daemon_process) + pargs := os.Args[1:] + cmd.Env = append(cmd.Env, daemon_taskargs+"="+strings.Join(pargs, ";")) + cmd.Start() +} + +func (g *GoDaemon) startTask() *exec.Cmd { + extraArgs, _ := os.LookupEnv(daemon_taskargs) + var cmd *exec.Cmd + execName, _ := os.Executable() + if extraArgs != "" { + eargs := strings.Split(extraArgs, ";") + cmd = exec.Command(execName, eargs...) + } else { + cmd = exec.Command(execName) + } + cmd.Env = append(cmd.Env, daemon_env_key+"="+daemon_task) + cmd.Start() + return cmd +} +func IsMaster() bool { + goDaemonEnv, _ := os.LookupEnv(daemon_env_key) + return goDaemonEnv == "" +} + +func IsDaemon() bool { + goDaemonEnv, _ := os.LookupEnv(daemon_env_key) + return goDaemonEnv == daemon_process +} + +func IsDaemonTask() bool { + goDaemonEnv, _ := os.LookupEnv(daemon_env_key) + return goDaemonEnv == daemon_task +} + +func NewGoDaemon(start, stop func(*GoDaemon)) *GoDaemon { + godaemon := &GoDaemon{} + + execName, _ := os.Executable() + if filepath.Ext(execName) != "" { + execName = strings.TrimSuffix(execName, filepath.Ext(execName)) + } + godaemon.pidFile = execName + ".pid" + godaemon.StartFn = start + godaemon.StopFn = stop + return godaemon +} diff --git a/test/flog.log b/test/flog.log new file mode 100644 index 0000000..2382fff --- /dev/null +++ b/test/flog.log @@ -0,0 +1,10 @@ +[2023-12-10 17:41:38] daemon : debug - Starting task new +[2023-12-10 17:41:39] task : debug - task running +[2023-12-10 17:41:40] task : debug - task running +[2023-12-10 17:41:41] task : debug - task running +[2023-12-10 17:41:42] task : debug - task running +[2023-12-10 17:41:43] task : debug - task running +[2023-12-10 17:41:44] task : debug - task running +[2023-12-10 17:41:45] daemon : debug - Stop it +[2023-12-10 17:41:45] task : debug - called stop +[2023-12-10 17:41:45] task : debug - To kill diff --git a/test/main.go b/test/main.go new file mode 100644 index 0000000..938fde3 --- /dev/null +++ b/test/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "time" + + "git.pyer.club/kingecg/godaemon" + "git.pyer.club/kingecg/gologger" +) + +var daemon *godaemon.GoDaemon + +func main() { + gologger.Configure(gologger.LoggersConfig{ + Appenders: map[string]gologger.LogAppenderConfig{ + "flog": { + Type: "file", + Options: map[string]interface{}{ + "file": "flog.log", + }, + }, + }, + Categories: map[string]gologger.LogConfig{ + "default": { + Appenders: []string{"flog"}, + Level: "debug", + }, + }, + }) + daemon := godaemon.NewGoDaemon(start, stop) + daemon.Start() +} + +func start(g *godaemon.GoDaemon) { + l := gologger.GetLogger("task") + for { + time.Sleep(time.Second * 1) + l.Debug("task running") + } +} + +func stop(g *godaemon.GoDaemon) { + l := gologger.GetLogger("task") + l.Debug("called stop") + if g == nil { + l.Debug("Daemon is nil") + return + } + if g.Running == nil { + l.Debug("task is nil") + return + } + if g != nil && g.Running != nil { + l.Debug("To kill") + g.Running.Process.Kill() + } +} diff --git a/test/test b/test/test new file mode 100755 index 0000000..f426daf Binary files /dev/null and b/test/test differ diff --git a/test/test.pid b/test/test.pid new file mode 100644 index 0000000..819d2e2 --- /dev/null +++ b/test/test.pid @@ -0,0 +1 @@ +38716 \ No newline at end of file diff --git a/vendor/git.pyer.club/kingecg/gologger/.gitignore b/vendor/git.pyer.club/kingecg/gologger/.gitignore new file mode 100644 index 0000000..f4d432a --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/.gitignore @@ -0,0 +1,17 @@ +# ---> Go +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + diff --git a/vendor/git.pyer.club/kingecg/gologger/README.md b/vendor/git.pyer.club/kingecg/gologger/README.md new file mode 100644 index 0000000..6833230 --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/README.md @@ -0,0 +1,3 @@ +# gologger + +a logger used in go \ No newline at end of file diff --git a/vendor/git.pyer.club/kingecg/gologger/console.go b/vendor/git.pyer.club/kingecg/gologger/console.go new file mode 100644 index 0000000..e5bfb65 --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/console.go @@ -0,0 +1,44 @@ +package gologger + +import ( + "fmt" +) + +const ( + ErrorTemplate = "\033[1;31m%s\033[0m" + WarnTemplate = "\033[1;33m%s\033[0m" + InfoTemplate = "\033[1;32m%s\033[0m" + DebugTemplate = "\033[1;34m%s\033[0m" + TraceTemplate = "\033[1;35m%s\033[0m" +) + +type ConsoleAppender struct { +} + +func (c *ConsoleAppender) GetName() string { + return "console" +} + +func (c *ConsoleAppender) Append(logEvent LogEvent) { + + logMsg := format(logEvent) + switch logEvent.Level { + case Error: + fmt.Printf(ErrorTemplate, logMsg) + case Warn: + fmt.Printf(WarnTemplate, logMsg) + case Info: + fmt.Printf(InfoTemplate, logMsg) + case Debug: + fmt.Printf(DebugTemplate, logMsg) + case Trace: + fmt.Printf(TraceTemplate, logMsg) + } +} +func makeConsoleAppender(appenderConfig LogAppenderConfig) LoggerAppender { + var appender LoggerAppender = &ConsoleAppender{} + return appender +} +func init() { + RegistAppender("console", makeConsoleAppender) +} diff --git a/vendor/git.pyer.club/kingecg/gologger/file.go b/vendor/git.pyer.club/kingecg/gologger/file.go new file mode 100644 index 0000000..5d56227 --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/file.go @@ -0,0 +1,47 @@ +package gologger + +import ( + "os" + "path/filepath" +) + +type FileAppender struct { + filePath string + file *os.File +} + +func (f *FileAppender) GetName() string { + return "FileAppender:" + f.filePath +} + +func (f *FileAppender) Append(logEvent LogEvent) { + if f.file == nil || int(f.file.Fd()) == -1 { + dirName := filepath.Dir(f.filePath) + _, err := os.Stat(dirName) + if err != nil && os.IsNotExist(err) { + os.MkdirAll(dirName, 0755) + } + f.file, _ = os.OpenFile(f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + } + + logMsg := format(logEvent) + f.file.WriteString(logMsg) + +} + +func makeFileAppender(appenderConfig LogAppenderConfig) LoggerAppender { + var logfile interface{} + var ok bool + logfile, ok = appenderConfig.Options["file"] + if !ok { + logfile = "default.log" + } + var ret LoggerAppender = &FileAppender{ + filePath: logfile.(string), + } + return ret +} + +func init() { + RegistAppender("file", makeFileAppender) +} diff --git a/vendor/git.pyer.club/kingecg/gologger/format.go b/vendor/git.pyer.club/kingecg/gologger/format.go new file mode 100644 index 0000000..6f1cde3 --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/format.go @@ -0,0 +1,23 @@ +package gologger + +import ( + "fmt" +) + +const logTemplate = "[%s] %s : %s - %s\n" + +func format(logEvent LogEvent) string { + data := logEvent.Ts.Format("2006-01-02 15:04:05") + msg := fmt.Sprint(logEvent.Data...) + ret := fmt.Sprintf(logTemplate, data, logEvent.Category, getLogLevelStr(logEvent.Level), msg) + return ret +} + +func getLogLevelStr(level int) string { + for name, slevel := range logLevelMap { + if slevel == level { + return name + } + } + return "Unknown" +} diff --git a/vendor/git.pyer.club/kingecg/gologger/main.go b/vendor/git.pyer.club/kingecg/gologger/main.go new file mode 100644 index 0000000..13c22cd --- /dev/null +++ b/vendor/git.pyer.club/kingecg/gologger/main.go @@ -0,0 +1,151 @@ +package gologger + +import ( + "strings" + "time" +) + +const ( + NoLog = iota + Error + Warn + Info + Debug + Trace +) + +var logLevelMap map[string]int = map[string]int{ + "off": NoLog, + "error": Error, + "warn": Warn, + "info": Info, + "debug": Debug, + "trace": Trace, +} +var loggerMap map[string]Logger = map[string]Logger{} +var appenderFactoryMap map[string]func(LogAppenderConfig) LoggerAppender = map[string]func(LogAppenderConfig) LoggerAppender{} +var appenders map[string]LoggerAppender = map[string]LoggerAppender{} +var loggerConfig LoggersConfig + +type LogAppenderConfig struct { + Type string `json:"type"` + Options map[string]interface{} `json:"options"` +} +type LogConfig struct { + Level string `json:"level"` + Appenders []string `json:"appenders"` +} + +type LoggersConfig struct { + Appenders map[string]LogAppenderConfig `json:"appenders"` + Categories map[string]LogConfig `json:"categories"` +} +type Logger struct { + category string + level int + appenders []LoggerAppender +} + +type LogEvent struct { + Category string + Ts time.Time + Level int + Data []interface{} +} + +type LoggerAppender interface { + GetName() string + Append(logEvent LogEvent) +} + +var consoleAppender LoggerAppender = &ConsoleAppender{} +var defaultLogger = &Logger{ + + level: Error, + appenders: []LoggerAppender{consoleAppender}, +} + +func (l *Logger) log(Level int, msg []interface{}) { + + if Level <= l.level { + now := time.Now() + logEvent := LogEvent{l.category, now, Level, msg} + for _, appender := range l.appenders { + appender.Append(logEvent) + } + // l.Appender.Append(logEvent) + // fmt.Println(now.Format("2006-01-02 15:04:05"), " ", l.Name, ": ", msg) + } +} + +func (l *Logger) Error(msg ...interface{}) { + l.log(Error, msg) +} + +func (l *Logger) Warn(msg ...interface{}) { + l.log(Warn, msg) +} + +func (l *Logger) Info(msg ...interface{}) { + l.log(Info, msg) +} + +func (l *Logger) Debug(msg ...interface{}) { + l.log(Debug, msg) +} + +func (l *Logger) Trace(msg ...interface{}) { + l.log(Trace, msg) +} +func GetLogger(name string) Logger { + if logger, ok := loggerMap[name]; ok { + return logger + } else { + logConfig, ok := loggerConfig.Categories[name] + if ok { + return makeLogger(name, logConfig) + } + if name == "default" { + return *defaultLogger + } + ret := GetLogger("default") + ret.category = name + return ret + } +} + +func makeLogger(name string, config LogConfig) Logger { + logger := &Logger{category: name} + levelstr := strings.ToLower(config.Level) + logger.level = logLevelMap[levelstr] + + if config.Appenders == nil || len(config.Appenders) == 0 { + logger.appenders = []LoggerAppender{consoleAppender} + } else { + logger.appenders = make([]LoggerAppender, len(config.Appenders)) + for i, appenderName := range config.Appenders { + logger.appenders[i] = appenders[appenderName] + } + } + loggerMap[name] = *logger + return *logger +} +func Configure(config LoggersConfig) { + loggerConfig = config + for name, appenderConfig := range loggerConfig.Appenders { + appenderFactory, ok := appenderFactoryMap[appenderConfig.Type] + if ok { + appenders[name] = appenderFactory(appenderConfig) + } else { + appenders[name] = &ConsoleAppender{} + } + } + for name, _ := range loggerConfig.Categories { + GetLogger(name) + } +} + +func RegistAppender(typeName string, appenderCreatCb func(LogAppenderConfig) LoggerAppender) { + + appenderFactoryMap[typeName] = appenderCreatCb +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..9ccaecd --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,3 @@ +# git.pyer.club/kingecg/gologger v1.0.1 +## explicit; go 1.19 +git.pyer.club/kingecg/gologger