package historyposter import ( "context" "io" "os" "os/signal" "path/filepath" "runtime" "syscall" "time" "git.eeqj.de/sneak/goutil" "git.eeqj.de/sneak/historyposter/store" "github.com/mattn/go-isatty" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" ) // HistoryPoster is the main app framework object type HistoryPoster struct { version string buildarch string startup time.Time appCtx context.Context shutdownFunc context.CancelFunc exitCode int store *store.Store logfh *os.File } // CLIEntry is the main entrypoint func CLIEntry(version, buildarch string) int { hp := new(HistoryPoster) hp.version = version hp.buildarch = buildarch hp.startup = time.Now() hp.exitCode = 0 c := make(chan os.Signal) signal.Ignore(syscall.SIGPIPE) signal.Notify(c, os.Interrupt, syscall.SIGTERM) hp.configure() hp.setupLogging() s, err := store.NewStore() if err != nil { hp.shutdown("cannot create state file: "+err.Error(), -1) return hp.exitCode } hp.store = s hp.appCtx, hp.shutdownFunc = context.WithCancel(context.Background()) go func() { // this sits and waits for an interrupt to be received sig := <-c log.Info().Msgf("signal received: %+v", sig) hp.shutdownFunc() }() return hp.runForever(hp.appCtx) } func (hp *HistoryPoster) configure() { viper.SetConfigName("historyposter") viper.SetConfigType("yaml") viper.AddConfigPath("/etc/historyposter") // path to look for the config file in viper.AddConfigPath("$HOME/.config/historyposter") // call multiple times to add many search paths viper.SetEnvPrefix("HISTORYPOSTER") viper.AutomaticEnv() viper.SetDefault("Debug", false) viper.SetDefault("Logfile", os.Getenv("HOME")+"/Library/Logs/historyposter/historyposter.log") if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; ignore error if desired } else { // Config file was found but another error was produced log.Panic(). Err(err). Msg("config file malformed") } } // if viper.GetBool("debug") { // pp.Print(viper.AllSettings()) // } } func (hp *HistoryPoster) runForever(ctx context.Context) int { log.Info().Msg("entering main loop") interval := 60 * time.Second timeout := 10 * time.Second ticker := time.NewTicker(interval) go func() { // do it once right now, without an insta-tick go func() { hp.postURLs(context.WithTimeout(ctx, timeout)) }() // then go do it repeatedly: for { select { case <-ticker.C: go func() { hp.postURLs(context.WithTimeout(ctx, timeout)) }() case <-ctx.Done(): ticker.Stop() return } } }() <-ctx.Done() hp.cleanup() log.Info().Msgf("exiting") hp.logfh.Close() return hp.exitCode } func (hp *HistoryPoster) cleanup() { log.Info().Msgf("begin cleanup") hp.store.Close() } func (hp *HistoryPoster) setupLogging() { // always log in UTC zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } log.Logger = log.With().Caller().Logger() zerolog.SetGlobalLevel(zerolog.InfoLevel) tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) consoleWriter := zerolog.NewConsoleWriter( func(w *zerolog.ConsoleWriter) { // Customize time format w.TimeFormat = time.RFC3339 }, ) var writers []io.Writer if tty { writers = append(writers, consoleWriter) } logfile := viper.GetString("Logfile") logfileDir := filepath.Dir(logfile) err := goutil.Mkdirp(logfileDir) if err != nil { log.Error().Err(err).Msg("unable to create log dir") } hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { panic("unable to open logfile: " + err.Error()) } writers = append(writers, hp.logfh) multi := zerolog.MultiLevelWriter(writers...) logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger() log.Logger = logger // FIXME get caller back in there zerolog.New(multi).Caller().Logger() if viper.GetBool("debug") { zerolog.SetGlobalLevel(zerolog.DebugLevel) } hp.identify() } func (hp *HistoryPoster) identify() { log.Info(). Str("version", hp.version). Str("buildarch", hp.buildarch). Str("os", runtime.GOOS). Msg("starting") } func (hp *HistoryPoster) shutdown(reason string, exitcode int) { log.Info().Msgf("shutting down: %s", reason) hp.exitCode = exitcode hp.shutdownFunc() }