227 lines
4.6 KiB
Go
227 lines
4.6 KiB
Go
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
|
|
}
|