package zaptg import ( "context" "encoding/json" "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" tb "gopkg.in/tucnak/telebot.v2" pb "penahub.gitlab.yandexcloud.net/external/trashlog.git/proto/generated" "strings" ) type levelEnabler struct { minLevel zapcore.Level } type TGCore struct { levelEnabler coreFields map[string]interface{} ctx context.Context bot *tb.Bot svcData *pb.SvcData ctxFields map[string]*pb.Value keyFields map[string]*pb.Value chatID int64 } func NewCore( ctx context.Context, minLevel zapcore.Level, token, version, commit string, buildTime, chatID int64, ) (*TGCore, error) { bot, err := tb.NewBot(tb.Settings{ Token: token, }) if err != nil { return nil, err } return &TGCore{ levelEnabler: levelEnabler{ minLevel: minLevel, }, coreFields: make(map[string]interface{}), ctx: ctx, svcData: &pb.SvcData{ BuildTime: uint64(buildTime), Version: version, Commit: commit, }, bot: bot, chatID: chatID, ctxFields: make(map[string]*pb.Value), keyFields: make(map[string]*pb.Value), }, nil } func (c *TGCore) WrapLogger(logger *zap.Logger) *zap.Logger { return logger.WithOptions( zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, c) }), zap.AddCallerSkip(1), ) } func (le *levelEnabler) Enabled(l zapcore.Level) bool { return l >= le.minLevel } func copyFieldsMap(src map[string]*pb.Value) map[string]*pb.Value { dst := make(map[string]*pb.Value, len(src)) for k, v := range src { dst[k] = v } return dst } func copyCoreMap(src map[string]any) map[string]any { dst := make(map[string]any, len(src)) for k, v := range src { dst[k] = v } return dst } func (c *TGCore) With(fields []zapcore.Field) zapcore.Core { fieldMap := fieldsToMap(fields) destCoreFields := copyCoreMap(c.coreFields) destKeyFields := copyFieldsMap(c.keyFields) destCtxFields := copyFieldsMap(c.ctxFields) for k, v := range fieldMap { destCoreFields[k] = v switch { case strings.HasPrefix(k, "Ctx"): destCtxFields[strings.TrimPrefix(k, "Ctx")] = convertValue(v) case strings.HasPrefix(k, "Key"): destKeyFields[strings.TrimPrefix(k, "Key")] = convertValue(v) default: destKeyFields[k] = convertValue(v) } } return &TGCore{ coreFields: destCoreFields, ctxFields: destCtxFields, keyFields: destKeyFields, chatID: c.chatID, svcData: c.svcData, ctx: c.ctx, bot: c.bot, } } func convertValue(v interface{}) *pb.Value { switch t := v.(type) { case string: return &pb.Value{Value: &pb.Value_Str{Str: t}} case int64: return &pb.Value{Value: &pb.Value_Num{Num: t}} case float64: return &pb.Value{Value: &pb.Value_Double{Double: float32(t)}} case bool: return &pb.Value{Value: &pb.Value_Flag{Flag: t}} } return nil } func (c *TGCore) Check( entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry, ) *zapcore.CheckedEntry { if c.levelEnabler.Enabled(entry.Level) { return checkedEntry.AddCore(entry, c) } return checkedEntry } func (c *TGCore) Write( entry zapcore.Entry, fields []zapcore.Field, ) error { fieldMap := fieldsToMap(fields) splittedMessage := strings.Split(entry.Message, "!") var msg string if len(splittedMessage) == 2 { msg = splittedMessage[1] } else { msg = entry.Message } keyFields, ctxFields := make(map[string]*pb.Value), make(map[string]*pb.Value) for k, v := range c.keyFields { keyFields[k] = v } for k, v := range c.ctxFields { ctxFields[k] = v } for k, v := range fieldMap { switch { case strings.HasPrefix(k, "Ctx"): ctxFields[strings.TrimPrefix(k, "Ctx")] = convertValue(v) case strings.HasPrefix(k, "Key"): keyFields[strings.TrimPrefix(k, "Key")] = convertValue(v) default: ctxFields[k] = convertValue(v) } } dataKey, _ := json.Marshal(keyFields) dataCtx, _ := json.Marshal(ctxFields) if _, err := c.bot.Send(&tb.Chat{ID: c.chatID}, fmt.Sprintf(` Событие: %s Уровень: %s Логгернейм: %s Время события: %s Версия: %s, Коммит: %s Файл: %s Ключевые: %s Контекстные: %s `, msg, entry.Level, entry.LoggerName, entry.Time.String(), c.svcData.Version, c.svcData.Commit, entry.Caller.File, string(dataKey), string(dataCtx), )); err != nil { fmt.Println(err) } return nil } func (c *TGCore) Sync() error { return nil } func fieldsToMap(fields []zapcore.Field) map[string]interface{} { enc := zapcore.NewMapObjectEncoder() for _, f := range fields { f.AddTo(enc) } m := make(map[string]interface{}) for k, v := range enc.Fields { m[k] = v } return m }