Skip to content

Instantly share code, notes, and snippets.

@alfonmga
Last active August 17, 2022 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alfonmga/18e3211ee0cf2b00e894225bd1bc8070 to your computer and use it in GitHub Desktop.
Save alfonmga/18e3211ee0cf2b00e894225bd1bc8070 to your computer and use it in GitHub Desktop.
Logrus -> Telegram
package logrus2telegram
import (
"fmt"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/sirupsen/logrus"
"errors"
"net/http"
)
// TelegramBotHook is a hook for Logrus logging library to send logs directly to Telegram.
type TelegramBotHook struct {
levels []logrus.Level
notifyOn map[logrus.Level]struct{}
client *http.Client
token string
parseMode string // <https://core.telegram.org/bots/api#formatting-options>
chatIDs []int64
format func(e *logrus.Entry) (string, error)
requestTimeout time.Duration
}
// config for hook.
type config struct {
levels []logrus.Level
notifyOn map[logrus.Level]struct{}
requestTimeout time.Duration
format func(e *logrus.Entry) (string, error)
client *http.Client
}
// Option configures the hook instance.
type Option func(*config) error
// NotifyOn enables notification in messages for specified log levels.
func NotifyOn(levels []logrus.Level) Option {
return func(h *config) error {
if len(levels) < 1 {
return errors.New("at least one level for notification is required")
}
for _, level := range levels {
h.notifyOn[level] = struct{}{}
}
return nil
}
}
// Levels allows to specify levels for the hook.
func Levels(levels []logrus.Level) Option {
return func(h *config) error {
if len(levels) < 1 {
return errors.New("at least one level is required")
}
h.levels = levels
return nil
}
}
// Format specifies the format function for the log entry.
func Format(format func(e *logrus.Entry) (string, error)) Option {
return func(h *config) error {
if format == nil {
return errors.New("the format function is nil")
}
h.format = format
return nil
}
}
// RequestTimeout specifies HTTP request timeout to Telegram API.
func RequestTimeout(requestTimeout time.Duration) Option {
return func(h *config) error {
if requestTimeout < 0 {
return errors.New("the request timeout must be positive")
}
h.requestTimeout = requestTimeout
return nil
}
}
func defaultFormat(entry *logrus.Entry) (string, error) {
m, err := entry.String()
if err != nil {
return "", fmt.Errorf("failed to serialize log entry: %w", err)
}
return m, nil
}
// NewHook creates new hook instance.
func NewHook(token string, chatIDs []int64, parseMode string, options ...Option) (*TelegramBotHook, error) {
if len(chatIDs) < 1 {
return nil, errors.New("at least one chatID is required")
}
c := &config{
levels: []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel, logrus.WarnLevel, logrus.InfoLevel},
format: defaultFormat,
notifyOn: make(map[logrus.Level]struct{}),
requestTimeout: 3 * time.Second,
client: &http.Client{},
}
for _, option := range options {
err := option(c)
if err != nil {
return nil, err
}
}
return &TelegramBotHook{
client: c.client,
token: token,
chatIDs: chatIDs,
format: c.format,
notifyOn: c.notifyOn,
levels: c.levels,
requestTimeout: c.requestTimeout,
parseMode: parseMode,
}, nil
}
// Fire sends the log entry to Telegram.
func (h *TelegramBotHook) Fire(entry *logrus.Entry) error {
text, err := h.format(entry)
if err != nil {
return fmt.Errorf("failed to format log entry: %w", err)
}
bot, err := tgbotapi.NewBotAPI(h.token)
if err != nil {
return fmt.Errorf("failed to create Telegram bot: %w", err)
}
for _, chatID := range h.chatIDs {
msg := tgbotapi.NewMessage(chatID, text)
msg.ParseMode = h.parseMode
msg.DisableNotification = !h.notify(entry.Level)
_, err := bot.Send(msg)
if err != nil {
return fmt.Errorf("failed to send message to chat %d: %w", chatID, err)
}
}
return nil
}
func (h *TelegramBotHook) notify(l logrus.Level) bool {
if len(h.notifyOn) == 0 {
return true
}
if _, notify := h.notifyOn[l]; notify {
return true
}
return false
}
// Levels define on which log levels this hook would trigger.
func (h *TelegramBotHook) Levels() []logrus.Level {
return h.levels
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment