0

Механики логгирования вынесены в отдельный модуль

This commit is contained in:
eleksir
2025-10-12 14:35:55 +03:00
parent 11e3eb1de0
commit 9baa9ec5a2
5 changed files with 149 additions and 89 deletions

View File

@@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"aleesa-misc-go/internal/log"
"aleesa-misc-go/internal/misc" "aleesa-misc-go/internal/misc"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
@@ -15,71 +15,24 @@ import (
func main() { func main() {
var ( var (
err error err error
loglevel slog.Level logfile *os.File
) )
misc.ReadConfig() misc.ReadConfig()
loghandler := os.Stderr
if misc.Config.Log != "" { 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 { 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) os.Exit(1)
} }
} else {
logfile = os.Stderr
} }
// no panic, no trace. log.Init(misc.Config.Loglevel, logfile)
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,
),
),
)
// Иницализируем клиента Редиски. // Иницализируем клиента Редиски.
misc.RedisClient = redis.NewClient(&redis.Options{ misc.RedisClient = redis.NewClient(&redis.Options{
@@ -100,7 +53,7 @@ func main() {
// Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений). // Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений).
ch := misc.Subscriber.Channel() ch := misc.Subscriber.Channel()
slog.Info("Aleesa-misc-go started") log.Info("Aleesa-misc-go started")
for msg := range ch { for msg := range ch {
if !misc.Shutdown { if !misc.Shutdown {

106
internal/log/log.go Normal file
View File

@@ -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: */

View File

@@ -3,9 +3,9 @@ package misc
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"log/slog"
"strings" "strings"
"aleesa-misc-go/internal/log"
) )
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки. // MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
@@ -15,29 +15,29 @@ func MsgParser(ctx context.Context, msg string) {
j rMsg 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 { 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 return
} }
// Validate our j. // Validate our j.
if exist := j.From; exist == "" { 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 return
} }
if exist := j.Chatid; exist == "" { 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 return
} }
if exist := j.Userid; exist == "" { 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 return
} }
@@ -45,19 +45,19 @@ func MsgParser(ctx context.Context, msg string) {
// j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально. // j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально.
if exist := j.Message; exist == "" { 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 return
} }
if exist := j.Plugin; exist == "" { 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 return
} }
if exist := j.Mode; exist == "" { 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 return
} }
@@ -82,7 +82,7 @@ func MsgParser(ctx context.Context, msg string) {
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение. // Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
if j.Misc.Fwdcnt > Config.ForwardsMax { 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 return
} }
@@ -222,16 +222,16 @@ func MsgParser(ctx context.Context, msg string) {
data, err := json.Marshal(message) data, err := json.Marshal(message)
if err != nil { 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 return
} }
// Заталкиваем наш json в редиску. // Заталкиваем наш json в редиску.
if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil { 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 { } 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))
} }
} }

View File

@@ -3,10 +3,11 @@ package misc
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"aleesa-misc-go/internal/log"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go"
) )
@@ -17,7 +18,7 @@ func ReadConfig() {
executablePath, err := os.Executable() executablePath, err := os.Executable()
if err != nil { 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)) configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
@@ -39,7 +40,7 @@ func ReadConfig() {
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата // Конфиг-файл длинноват для конфига, попробуем следующего кандидата
if fileInfo.Size() > 65535 { 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 continue
} }
@@ -48,7 +49,7 @@ func ReadConfig() {
// Не удалось прочитать, попробуем следующего кандидата // Не удалось прочитать, попробуем следующего кандидата
if err != nil { 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 continue
} }
@@ -65,7 +66,7 @@ func ReadConfig() {
// Не удалось распарсить - попробуем следующего кандидата // Не удалось распарсить - попробуем следующего кандидата
if err != nil { 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 continue
} }
@@ -74,13 +75,13 @@ func ReadConfig() {
// Не удалось преобразовать map-ку в json // Не удалось преобразовать map-ку в json
if err != nil { 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 continue
} }
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { 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 continue
} }
@@ -105,7 +106,7 @@ func ReadConfig() {
// sampleConfig.Log = "" if not set // sampleConfig.Log = "" if not set
if sampleConfig.Channel == "" { 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 == "" { 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 { if sampleConfig.ForwardsMax == 0 {
@@ -140,13 +141,13 @@ func ReadConfig() {
Config = sampleConfig Config = sampleConfig
configLoaded = true configLoaded = true
slog.Info(fmt.Sprintf("Using %s as config file", location)) log.Infof("Using %s as config file", location)
break break
} }
if !configLoaded { if !configLoaded {
slog.Error("Config was not loaded! Refusing to start.") log.Error("Config was not loaded! Refusing to start.")
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -1,27 +1,27 @@
package misc package misc
import ( import (
"fmt"
"log/slog"
"os" "os"
"syscall" "syscall"
"aleesa-misc-go/internal/log"
) )
// SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения. // SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения.
func SigHandler() { func SigHandler() {
var err error var err error
slog.Info("Install signal handler") log.Info("Install signal handler")
for { for {
var s = <-SigChan var s = <-SigChan
switch s { switch s {
case syscall.SIGINT: case syscall.SIGINT:
slog.Info("Got SIGINT, quitting") log.Info("Got SIGINT, quitting")
case syscall.SIGTERM: case syscall.SIGTERM:
slog.Info("Got SIGTERM, quitting") log.Info("Got SIGTERM, quitting")
case syscall.SIGQUIT: case syscall.SIGQUIT:
slog.Info("Got SIGQUIT, quitting") log.Info("Got SIGQUIT, quitting")
// Заходим на новую итерацию, если у нас "неинтересный" сигнал. // Заходим на новую итерацию, если у нас "неинтересный" сигнал.
default: default:
@@ -33,11 +33,11 @@ func SigHandler() {
// Отпишемся от всех каналов и закроем коннект к редиске. // Отпишемся от всех каналов и закроем коннект к редиске.
if err = Subscriber.Unsubscribe(Ctx); err != nil { 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 { 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) os.Exit(0)