diff --git a/cmd/aleesa-misc-go/main.go b/cmd/aleesa-misc-go/main.go index 49f5020..02f6ce4 100644 --- a/cmd/aleesa-misc-go/main.go +++ b/cmd/aleesa-misc-go/main.go @@ -2,12 +2,12 @@ package main import ( "fmt" - "log/slog" "os" "os/signal" "syscall" "time" + "aleesa-misc-go/internal/log" "aleesa-misc-go/internal/misc" "github.com/go-redis/redis/v8" @@ -15,71 +15,24 @@ import ( func main() { var ( - err error - loglevel slog.Level + err error + logfile *os.File ) misc.ReadConfig() - loghandler := os.Stderr - if misc.Config.Log != "" { - loghandler, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + logfile, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - slog.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err)) + log.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err)) os.Exit(1) } + } else { + logfile = os.Stderr } - // no panic, no trace. - switch misc.Config.Loglevel { - case "error": - loglevel = slog.LevelError - - case "warn": - loglevel = slog.LevelWarn - - case "info": - loglevel = slog.LevelInfo - - case "debug": - loglevel = slog.LevelDebug - - default: - loglevel = slog.LevelInfo - - } - - opts := &slog.HandlerOptions{ - // Use the ReplaceAttr function on the handler options - // to be able to replace any single attribute in the log output - ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { - // check that we are handling the time key - if a.Key != slog.TimeKey { - return a - } - - t := a.Value.Time() - - // change the value from a time.Time to a String - // where the string has the correct time format. - a.Value = slog.StringValue(t.Format(time.DateTime)) - - return a - }, - - Level: loglevel, - } - - slog.SetDefault( - slog.New( - slog.NewTextHandler( - loghandler, - opts, - ), - ), - ) + log.Init(misc.Config.Loglevel, logfile) // Иницализируем клиента Редиски. misc.RedisClient = redis.NewClient(&redis.Options{ @@ -100,7 +53,7 @@ func main() { // Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений). ch := misc.Subscriber.Channel() - slog.Info("Aleesa-misc-go started") + log.Info("Aleesa-misc-go started") for msg := range ch { if !misc.Shutdown { diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000..567334a --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,106 @@ +package log + +import ( + "fmt" + "log/slog" + "os" + "time" +) + +// Init setup logger stuff. +// level can be error, warn, info, debug if something other supplied info level selected. +// fileDescriptor should be opened before supplying it to Init(). +func Init(level string, fileDescriptor *os.File) { + var loglevel slog.Level + + // no panic, no trace. + switch level { + case "error": + loglevel = slog.LevelError + + case "warn": + loglevel = slog.LevelWarn + + case "info": + loglevel = slog.LevelInfo + + case "debug": + loglevel = slog.LevelDebug + + default: + loglevel = slog.LevelInfo + + } + + opts := &slog.HandlerOptions{ + // Use the ReplaceAttr function on the handler options + // to be able to replace any single attribute in the log output + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + // check that we are handling the time key + if a.Key != slog.TimeKey { + return a + } + + t := a.Value.Time() + + // change the value from a time.Time to a String + // where the string has the correct time format. + a.Value = slog.StringValue(t.Format(time.DateTime)) + + return a + }, + + Level: loglevel, + } + + slog.SetDefault( + slog.New( + slog.NewTextHandler( + fileDescriptor, + opts, + ), + ), + ) +} + +// Error passes message directly to slog.Error(). +func Error(message string) { + slog.Error(message) +} + +// Errorf logs message on error log level and allows to supply arguments in printf() style. +func Errorf(format string, a ...any) { + slog.Error(fmt.Sprintf(format, a...)) +} + +// Warn passes message directly to slog.Warn(). +func Warn(message string) { + slog.Warn(message) +} + +// Warnf logs message on warn log level and allows to supply arguments in printf() style. +func Warnf(format string, a ...any) { + slog.Warn(fmt.Sprintf(format, a...)) +} + +// Info passes message directly to slog.Info(). +func Info(message string) { + slog.Info(message) +} + +// Infof logs message on info log level and allows to supply arguments in printf() style. +func Infof(format string, a ...any) { + slog.Info(fmt.Sprintf(format, a...)) +} + +// Debug passes message directly to slog.Debug(). +func Debug(message string) { + slog.Debug(message) +} + +// Debugf logs message on debug log level and allows to supply arguments in printf() style. +func Debugf(format string, a ...any) { + slog.Debug(fmt.Sprintf(format, a...)) +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/misc/msgParser.go b/internal/misc/msgParser.go index 9c41f23..c0dae69 100644 --- a/internal/misc/msgParser.go +++ b/internal/misc/msgParser.go @@ -3,9 +3,9 @@ package misc import ( "context" "encoding/json" - "fmt" - "log/slog" "strings" + + "aleesa-misc-go/internal/log" ) // MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки. @@ -15,29 +15,29 @@ func MsgParser(ctx context.Context, msg string) { j rMsg ) - slog.Debug(fmt.Sprintf("Incomming raw json: %s", msg)) + log.Debugf("Incomming raw json: %s", msg) if err := json.Unmarshal([]byte(msg), &j); err != nil { - slog.Warn(fmt.Sprintf("Unable to to parse message from redis channel: %s", err)) + log.Warnf("Unable to to parse message from redis channel: %s", err) return } // Validate our j. if exist := j.From; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no from field: %s", msg)) + log.Warnf("Incorrect msg from redis, no from field: %s", msg) return } if exist := j.Chatid; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no chatid field: %s", msg)) + log.Warnf("Incorrect msg from redis, no chatid field: %s", msg) return } if exist := j.Userid; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no userid field: %s", msg)) + log.Warnf("Incorrect msg from redis, no userid field: %s", msg) return } @@ -45,19 +45,19 @@ func MsgParser(ctx context.Context, msg string) { // j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально. if exist := j.Message; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no message field: %s", msg)) + log.Warnf("Incorrect msg from redis, no message field: %s", msg) return } if exist := j.Plugin; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no plugin field: %s", msg)) + log.Warnf("Incorrect msg from redis, no plugin field: %s", msg) return } if exist := j.Mode; exist == "" { - slog.Warn(fmt.Sprintf("Incorrect msg from redis, no mode field: %s", msg)) + log.Warnf("Incorrect msg from redis, no mode field: %s", msg) return } @@ -82,7 +82,7 @@ func MsgParser(ctx context.Context, msg string) { // Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение. if j.Misc.Fwdcnt > Config.ForwardsMax { - slog.Warn(fmt.Sprintf("Discarding msg with fwd_cnt exceeding max value: %s", msg)) + log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg) return } @@ -222,16 +222,16 @@ func MsgParser(ctx context.Context, msg string) { data, err := json.Marshal(message) if err != nil { - slog.Warn(fmt.Sprintf("Unable to to serialize message for redis: %s", err)) + log.Warnf("Unable to to serialize message for redis: %s", err) return } // Заталкиваем наш json в редиску. if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil { - slog.Warn(fmt.Sprintf("Unable to send data to redis channel %s: %s", sendTo, err)) + log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err) } else { - slog.Debug(fmt.Sprintf("Send msg to redis channel %s: %s", sendTo, string(data))) + log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data)) } } diff --git a/internal/misc/readConfig.go b/internal/misc/readConfig.go index 32cd8aa..e0cbfb3 100644 --- a/internal/misc/readConfig.go +++ b/internal/misc/readConfig.go @@ -3,10 +3,11 @@ package misc import ( "encoding/json" "fmt" - "log/slog" "os" "path/filepath" + "aleesa-misc-go/internal/log" + "github.com/hjson/hjson-go" ) @@ -17,7 +18,7 @@ func ReadConfig() { executablePath, err := os.Executable() if err != nil { - slog.Error(fmt.Sprintf("Unable to get current executable path: %s", err)) + log.Errorf("Unable to get current executable path: %s", err) } configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) @@ -39,7 +40,7 @@ func ReadConfig() { // Конфиг-файл длинноват для конфига, попробуем следующего кандидата if fileInfo.Size() > 65535 { - slog.Warn(fmt.Sprintf("Config file %s is too long for config, skipping", location)) + log.Warnf("Config file %s is too long for config, skipping", location) continue } @@ -48,7 +49,7 @@ func ReadConfig() { // Не удалось прочитать, попробуем следующего кандидата if err != nil { - slog.Warn(fmt.Sprintf("Skip reading config file %s: %s", location, err)) + log.Warnf("Skip reading config file %s: %s", location, err) continue } @@ -65,7 +66,7 @@ func ReadConfig() { // Не удалось распарсить - попробуем следующего кандидата if err != nil { - slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) + log.Warnf("Skip parsing config file %s: %s", location, err) continue } @@ -74,13 +75,13 @@ func ReadConfig() { // Не удалось преобразовать map-ку в json if err != nil { - slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) + log.Warnf("Skip parsing config file %s: %s", location, err) continue } if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { - slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) + log.Warnf("Skip parsing config file %s: %s", location, err) continue } @@ -105,7 +106,7 @@ func ReadConfig() { // sampleConfig.Log = "" if not set if sampleConfig.Channel == "" { - slog.Error(fmt.Sprintf("Channel field in config file %s must be set", location)) + log.Errorf("Channel field in config file %s must be set", location) } // Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения @@ -130,7 +131,7 @@ func ReadConfig() { } if sampleConfig.Csign == "" { - slog.Error(fmt.Sprintf("Csign field in config file %s must be set", location)) + log.Errorf("Csign field in config file %s must be set", location) } if sampleConfig.ForwardsMax == 0 { @@ -140,13 +141,13 @@ func ReadConfig() { Config = sampleConfig configLoaded = true - slog.Info(fmt.Sprintf("Using %s as config file", location)) + log.Infof("Using %s as config file", location) break } if !configLoaded { - slog.Error("Config was not loaded! Refusing to start.") + log.Error("Config was not loaded! Refusing to start.") os.Exit(1) } } diff --git a/internal/misc/sigHandler.go b/internal/misc/sigHandler.go index ae41306..884a154 100644 --- a/internal/misc/sigHandler.go +++ b/internal/misc/sigHandler.go @@ -1,27 +1,27 @@ package misc import ( - "fmt" - "log/slog" "os" "syscall" + + "aleesa-misc-go/internal/log" ) // SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения. func SigHandler() { var err error - slog.Info("Install signal handler") + log.Info("Install signal handler") for { var s = <-SigChan switch s { case syscall.SIGINT: - slog.Info("Got SIGINT, quitting") + log.Info("Got SIGINT, quitting") case syscall.SIGTERM: - slog.Info("Got SIGTERM, quitting") + log.Info("Got SIGTERM, quitting") case syscall.SIGQUIT: - slog.Info("Got SIGQUIT, quitting") + log.Info("Got SIGQUIT, quitting") // Заходим на новую итерацию, если у нас "неинтересный" сигнал. default: @@ -33,11 +33,11 @@ func SigHandler() { // Отпишемся от всех каналов и закроем коннект к редиске. if err = Subscriber.Unsubscribe(Ctx); err != nil { - slog.Error(fmt.Sprintf("Unable to unsubscribe from redis channels cleanly: %s", err)) + log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err) } if err = Subscriber.Close(); err != nil { - slog.Error(fmt.Sprintf("Unable to close redis connection cleanly: %s", err)) + log.Errorf("Unable to close redis connection cleanly: %s", err) } os.Exit(0)