From 89d8e59f8f45e4570413b5f19a4d984c223d7015 Mon Sep 17 00:00:00 2001 From: Ali Afsharzadeh Date: Sat, 28 Mar 2026 16:19:21 +0330 Subject: [PATCH 1/3] Add timeout option for telegram notifier Signed-off-by: Ali Afsharzadeh --- docs/configuration.md | 6 ++++ notify/telegram/config.go | 5 +++ notify/telegram/telegram.go | 4 +++ notify/telegram/telegram_test.go | 62 ++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index eec8781b57..93bc2352ae 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1900,6 +1900,12 @@ attributes: # The HTTP client's configuration. [ http_config: | default = global.http_config ] + +# The maximum time to wait for a telegram request to complete, before failing the +# request and allowing it to be retried. The default value of 0s indicates that +# no timeout should be applied. +# NOTE: This will have no effect if set higher than the group_interval. +[ timeout: | default = 0s ] ``` ### `` diff --git a/notify/telegram/config.go b/notify/telegram/config.go index d069cbd6e5..5f02739cd4 100644 --- a/notify/telegram/config.go +++ b/notify/telegram/config.go @@ -15,6 +15,7 @@ package telegram import ( "errors" + "time" commoncfg "github.com/prometheus/common/config" @@ -45,6 +46,10 @@ type TelegramConfig struct { Message string `yaml:"message,omitempty" json:"message,omitempty"` DisableNotifications bool `yaml:"disable_notifications,omitempty" json:"disable_notifications,omitempty"` ParseMode string `yaml:"parse_mode,omitempty" json:"parse_mode,omitempty"` + + // Timeout is the maximum time allowed to invoke the telegram. Setting this to 0 + // does not impose a timeout. + Timeout time.Duration `yaml:"timeout" json:"timeout"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/notify/telegram/telegram.go b/notify/telegram/telegram.go index 35b8fb3288..c822ff00eb 100644 --- a/notify/telegram/telegram.go +++ b/notify/telegram/telegram.go @@ -50,6 +50,10 @@ func New(conf *TelegramConfig, t *template.Template, l *slog.Logger, httpOpts .. return nil, err } + if conf.Timeout > 0 { + httpclient.Timeout = conf.Timeout + } + client, err := createTelegramClient(conf.APIUrl.String(), conf.ParseMode, httpclient) if err != nil { return nil, err diff --git a/notify/telegram/telegram_test.go b/notify/telegram/telegram_test.go index 56dc3a6bcd..b0221dc8e0 100644 --- a/notify/telegram/telegram_test.go +++ b/notify/telegram/telegram_test.go @@ -352,3 +352,65 @@ func TestTelegramNotifyRedactURL(t *testing.T) { require.NotContains(t, err.Error(), token, "bot token leaked in API error") }) } + +func TestTelegramTimeout(t *testing.T) { + token := "secret" + + tests := []struct { + name string + latency time.Duration + timeout time.Duration + wantErr bool + }{ + { + name: "success", + latency: 100 * time.Millisecond, + timeout: 120 * time.Millisecond, + wantErr: false, + }, + { + name: "timeout", + latency: 100 * time.Millisecond, + timeout: 80 * time.Millisecond, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/bot"+token+"/sendMessage", r.URL.Path) + _, err := io.ReadAll(r.Body) + require.NoError(t, err) + time.Sleep(tc.latency) + w.Write([]byte(`{"ok":true,"result":{"chat":{}}}`)) + })) + defer srv.Close() + u, _ := url.Parse(srv.URL) + + cfg := &TelegramConfig{ + Message: "test", + HTTPConfig: &commoncfg.HTTPClientConfig{}, + BotToken: commoncfg.Secret(token), + Timeout: tc.timeout, + APIUrl: &amcommoncfg.URL{URL: u}, + } + + notifier, err := New(cfg, test.CreateTmpl(t), promslog.NewNopLogger()) + require.NoError(t, err) + + ctx := context.Background() + ctx = notify.WithGroupKey(ctx, "1") + + testAlert := &alert.Alert{ + Alert: model.Alert{ + StartsAt: time.Now(), + EndsAt: time.Now().Add(time.Hour), + }, + } + + _, err = notifier.Notify(ctx, testAlert) + require.Equal(t, tc.wantErr, err != nil) + }) + } +} From c0293d80b4f777340b33b391a0db8cc4a242b401 Mon Sep 17 00:00:00 2001 From: Ali Afsharzadeh Date: Mon, 20 Jul 2026 17:23:00 +0330 Subject: [PATCH 2/3] Fix deprecation warning Signed-off-by: Ali Afsharzadeh --- notify/telegram/telegram_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notify/telegram/telegram_test.go b/notify/telegram/telegram_test.go index b0221dc8e0..910d619c33 100644 --- a/notify/telegram/telegram_test.go +++ b/notify/telegram/telegram_test.go @@ -33,9 +33,9 @@ import ( amcommoncfg "github.com/prometheus/alertmanager/config/common" + "github.com/prometheus/alertmanager/alert" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify/test" - "github.com/prometheus/alertmanager/types" ) func TestTelegramUnmarshal(t *testing.T) { @@ -182,7 +182,7 @@ func TestTelegramNotify(t *testing.T) { defer cancel() ctx = notify.WithGroupKey(ctx, "1") - retry, err := notifier.Notify(ctx, []*types.Alert{ + retry, err := notifier.Notify(ctx, []*alert.Alert{ { Alert: model.Alert{ Labels: model.LabelSet{ @@ -261,7 +261,7 @@ func TestTelegramNotifyFailureReason(t *testing.T) { defer cancel() ctx = notify.WithGroupKey(ctx, "1") - retry, err := notifier.Notify(ctx, []*types.Alert{ + retry, err := notifier.Notify(ctx, []*alert.Alert{ { Alert: model.Alert{ Labels: model.LabelSet{"lbl1": "val1"}, From f12721f26b2876ed0cdb3c223abf2dc93e390fcc Mon Sep 17 00:00:00 2001 From: Ali Afsharzadeh Date: Sun, 30 Aug 2026 14:46:24 +0330 Subject: [PATCH 3/3] Report telegram client timeout with a standard error message Signed-off-by: Ali Afsharzadeh --- notify/telegram/telegram.go | 3 +++ notify/telegram/telegram_test.go | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/notify/telegram/telegram.go b/notify/telegram/telegram.go index c822ff00eb..645f12dcb5 100644 --- a/notify/telegram/telegram.go +++ b/notify/telegram/telegram.go @@ -123,6 +123,9 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err ParseMode: n.conf.ParseMode, }) if err != nil { + if n.conf.Timeout > 0 && errors.Is(err, context.DeadlineExceeded) { + err = fmt.Errorf("configured telegram timeout reached (%s)", n.conf.Timeout) + } return true, wrapWithFailureReason(notify.RedactURL(err)) } logger.Debug("Telegram message successfully published", "message_id", message.ID, "chat_id", message.Chat.ID) diff --git a/notify/telegram/telegram_test.go b/notify/telegram/telegram_test.go index 910d619c33..316d8ddab5 100644 --- a/notify/telegram/telegram_test.go +++ b/notify/telegram/telegram_test.go @@ -16,6 +16,7 @@ package telegram import ( "context" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" @@ -307,7 +308,7 @@ func TestTelegramNotifyRedactURL(t *testing.T) { defer cancel() ctx = notify.WithGroupKey(ctx, "1") - retry, err := notifier.Notify(ctx, &types.Alert{ + retry, err := notifier.Notify(ctx, &alert.Alert{ Alert: model.Alert{Labels: model.LabelSet{"alertname": "test"}}, }) require.True(t, retry) @@ -344,7 +345,7 @@ func TestTelegramNotifyRedactURL(t *testing.T) { defer cancel() ctx = notify.WithGroupKey(ctx, "1") - retry, err := notifier.Notify(ctx, &types.Alert{ + retry, err := notifier.Notify(ctx, &alert.Alert{ Alert: model.Alert{Labels: model.LabelSet{"alertname": "test"}}, }) require.True(t, retry) @@ -411,6 +412,9 @@ func TestTelegramTimeout(t *testing.T) { _, err = notifier.Notify(ctx, testAlert) require.Equal(t, tc.wantErr, err != nil) + if tc.wantErr { + require.EqualError(t, err, fmt.Sprintf("configured telegram timeout reached (%s)", tc.timeout)) + } }) } }