zap2telegram is a fantastic way to centralize your program's zap logs by sending them to a Telegram chat.
package main import ( "context" "fmt" "os" "strings" "time" "github.com/alfonmga/zap2telegram" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) const appName = "acme-api" const ( tgBotAccessToken = "<telegram-bot-access-token>" tgChatID = -1 tgMsgParseMode = "MarkdownV2" ) func main() { zap2telegramCore, err := zap2telegram.NewTelegramCore( tgBotAccessToken, []int64{tgChatID}, zap2telegram.WithLevel(zapcore.InfoLevel), // send only Info and above logs to Telegram zap2telegram.WithNotificationOn([]zapcore.Level{zap.ErrorLevel, zap.PanicLevel, zap.FatalLevel}), // enable message notification only this levels zap2telegram.WithQueue(context.Background(), 11*time.Second, 330), // use queue to send messages to Telegram every 11 seconds and set queue size to 330 messages at most zap2telegram.WithParseMode(tgMsgParseMode), zap2telegram.WithFormatter(func(e zapcore.Entry, fields []zapcore.Field) string { escapedAppName := tgbotapi.EscapeText(tgMsgParseMode, appName) escapedLoggerName := tgbotapi.EscapeText(tgMsgParseMode, e.LoggerName) escapedCaller := tgbotapi.EscapeText(tgMsgParseMode, e.Caller.TrimmedPath()) escapedMessage := tgbotapi.EscapeText(tgMsgParseMode, e.Message) // [INFO] acme-api // Logger: main // Caller: main.go:10 // Message: Some message here // user_id=12345 // Stacktrace: ... msg := fmt.Sprintf( "\\[%s\\] _%s_\nLogger: %s\nCaller: %s\nMessage: *%s*", strings.ToUpper(e.Level.String()), escapedAppName, escapedLoggerName, escapedCaller, escapedMessage, ) // add fields to the message msgFields := "" for _, field := range fields { enc := zapcore.NewMapObjectEncoder() field.AddTo(enc) for k, v := range enc.Fields { if k == "app_name" { continue // skip app_name field because it is already in the message } escapedK := tgbotapi.EscapeText(tgMsgParseMode, k) escapedV := tgbotapi.EscapeText(tgMsgParseMode, fmt.Sprintf("%+v", v)) msgField := fmt.Sprintf("%s\\=`%s`", escapedK, escapedV) if msgFields != "" { msgFields += " " // add leading space if there are already fields } msgFields += msgField } } if msgFields != "" { msg += fmt.Sprintf("\n%s", msgFields) } if e.Stack != "" { msg += fmt.Sprintf("\nLogger stacktrace: `%s`", tgbotapi.EscapeText(tgMsgParseMode, string(e.Stack))) } return msg }), ) if err != nil { panic(fmt.Errorf("failed to initialize zap2telegramCore: %+v", err)) } logger := zap.New(zapcore.NewTee( zap2telegramCore, // Console stderr output > warn level zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), zapcore.Lock(os.Stderr), zap.LevelEnablerFunc(func(level zapcore.Level) bool { return level > zapcore.WarnLevel }), ), // Console stdout output <= warn level zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), zapcore.Lock(os.Stdout), zap.LevelEnablerFunc(func(level zapcore.Level) bool { return level <= zapcore.WarnLevel }), ), )).WithOptions(zap.AddStacktrace(zap.ErrorLevel), zap.AddCaller()).With(zap.String("app_name", appName)).Named("main") defer logger.Sync() // send logs to Telegram before the program exit (this is only supported if you're using the `WithQueue` option). If you prefer, you can use the `WithoutAsyncOpt` option for synchronous sending (blocking) logger.Warn("take a look at this log message, something important may be happening!") logger.Error("something went wrong", zap.String("user_id", "12345")) }