Compare commits

...

5 Commits

Author SHA1 Message Date
nityanandagohain
be8147431d chore: minor changes 2026-09-22 12:45:28 +05:30
nityanandagohain
57f9774fb1 chore: address comments 2026-09-22 12:21:01 +05:30
Nityananda Gohain
b7abfba3e1 Merge branch 'main' into issue_6021_1 2026-09-22 10:06:54 +05:30
nityanandagohain
8004b62493 chore: update alertmanager template 2026-09-17 15:10:13 +05:30
nityanandagohain
631e6eefde chore: update alertmanager template 2026-09-16 11:13:25 +05:30
13 changed files with 98 additions and 16 deletions

View File

@@ -20,7 +20,7 @@ export const SlackInitialConfig: Partial<SlackChannel> = {
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}}{{ if match "/ai-observability" .Annotations.related_traces -}} View in <{{ .Annotations.related_traces }}|ai traces explorer> {{- else -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end }}{{- end}}
*Details:*
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
@@ -137,7 +137,7 @@ export const JsmOpsInitialConfig: Partial<JsmOpsChannel> = {
{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})
{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})
{{ end }}{{ if .Annotations.related_traces }}{{ if match "/ai-observability" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
{{ end }}{{ end }}`,
priority:
@@ -163,7 +163,7 @@ export const IncidentIOInitialConfig: Partial<IncidentIOChannel> = {
{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})
{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})
{{ end }}{{ if .Annotations.related_traces }}{{ if match "/ai-observability" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
{{ end }}{{ end }}`,
};

View File

@@ -25,7 +25,7 @@ export const editAlertChannelInitialValue = {
export const slackTitleDefaultValue = `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }} {{- if gt (len .CommonLabels) (len .GroupLabels) -}} {{" "}}( {{- with .CommonLabels.Remove .GroupLabels.Names }} {{- range $index, $label := .SortedPairs -}} {{ if $index }}, {{ end }} {{- $label.Name }}="{{ $label.Value -}}" {{- end }} {{- end -}} ) {{- end }}`;
export const slackDescriptionDefaultValue = `{{ range .Alerts -}} *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} *Summary:* {{ .Annotations.summary }} *Description:* {{ .Annotations.description }} *RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}} *RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}} *Details:* {{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }} {{ end }} {{ end }}`;
export const slackDescriptionDefaultValue = `{{ range .Alerts -}} *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} *Summary:* {{ .Annotations.summary }} *Description:* {{ .Annotations.description }} *RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}} *RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}}{{ if match "/ai-observability" .Annotations.related_traces -}} View in <{{ .Annotations.related_traces }}|ai traces explorer> {{- else -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end }}{{- end}} *Details:* {{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }} {{ end }} {{ end }}`;
export const googleChatTitleDefaultValue = `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }}`;

View File

@@ -493,7 +493,7 @@ func appendRelatedLinkButtons(alerts []*types.Alert, bodies []string) {
bodies[i] += htmlButton("View Related Logs", string(link))
}
if link := alerts[i].Annotations[ruletypes.AnnotationRelatedTraces]; link != "" {
bodies[i] += htmlButton("View Related Traces", string(link))
bodies[i] += htmlButton(ruletypes.RelatedTracesLabel(string(link)), string(link))
}
}
}

View File

@@ -249,7 +249,8 @@ func relatedButtons(alert *types.Alert) []button {
}
}
add("View Related Logs", string(alert.Annotations[ruletypes.AnnotationRelatedLogs]))
add("View Related Traces", string(alert.Annotations[ruletypes.AnnotationRelatedTraces]))
traces := string(alert.Annotations[ruletypes.AnnotationRelatedTraces])
add(ruletypes.RelatedTracesLabel(traces), traces)
return buttons
}

View File

@@ -343,6 +343,16 @@ func TestGoogleChatLinkButtons(t *testing.T) {
"View Related Traces": "https://signoz.example/traces",
},
},
{
name: "ai traces link",
labels: model.LabelSet{"alertname": "X"},
annotations: model.LabelSet{
ruletypes.AnnotationRelatedTraces: "https://signoz.example/ai-observability/explorer?q=1",
},
wantButtons: map[string]string{
"View Related AI Traces": "https://signoz.example/ai-observability/explorer?q=1",
},
},
{
name: "no links → no buttons",
labels: model.LabelSet{"alertname": "X"},

View File

@@ -214,3 +214,25 @@ func TestNotifyTruncatesLongDescription(t *testing.T) {
assert.LessOrEqual(t, len(desc), maxDescriptionLenRunes)
assert.Equal(t, '…', desc[len(desc)-1])
}
func TestNotifyRelatedTracesLabel(t *testing.T) {
cases := []struct {
name string
link string
want string
}{
{name: "traces explorer", link: "https://signoz.example/traces-explorer?q=1", want: "[View related traces]"},
{name: "ai traces explorer", link: "https://signoz.example/ai-observability/explorer?q=1", want: "[View related AI traces]"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := newMockIncidentIO(t)
a := alert(true)
a.Annotations["related_traces"] = model.LabelValue(c.link)
_, err := newNotifier(t, m).Notify(ctx(), a)
require.NoError(t, err)
assert.Contains(t, m.lastEvent(t).Description, c.want+"("+c.link+")")
})
}
}

View File

@@ -260,7 +260,8 @@ func deepLinks(alerts []*types.Alert) map[string]any {
}
add("Open in SigNoz", string(a.Labels[ruletypes.LabelRuleSource]))
add("View Related Logs", string(a.Annotations[ruletypes.AnnotationRelatedLogs]))
add("View Related Traces", string(a.Annotations[ruletypes.AnnotationRelatedTraces]))
traces := string(a.Annotations[ruletypes.AnnotationRelatedTraces])
add(ruletypes.RelatedTracesLabel(traces), traces)
if len(parts) == 0 {
return nil
}

View File

@@ -273,7 +273,7 @@ func buildRelatedLinkActions(alert *types.Alert) []config.SlackAction {
actions = append(actions, config.SlackAction{Type: "button", Text: "View Related Logs", URL: string(link)})
}
if link := alert.Annotations[ruletypes.AnnotationRelatedTraces]; link != "" {
actions = append(actions, config.SlackAction{Type: "button", Text: "View Related Traces", URL: string(link)})
actions = append(actions, config.SlackAction{Type: "button", Text: ruletypes.RelatedTracesLabel(string(link)), URL: string(link)})
}
return actions
}

View File

@@ -131,7 +131,7 @@ Description: Request rate exceeded 10k/s`},
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}}{{ if match "/ai-observability" .Annotations.related_traces -}} View in <{{ .Annotations.related_traces }}|ai traces explorer> {{- else -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end }}{{- end}}
*Details:*
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
@@ -157,6 +157,31 @@ Description: Request rate exceeded 10k/s`},
" "},
wantIsDefaultBody: true,
},
{
// AI trace alerts link to the ai-observability explorer; the default
// body template labels that link differently from a plain traces link.
name: "old template: ai traces link labelled ai traces explorer",
alerts: []*types.Alert{
createAlert(
map[string]string{ruletypes.LabelAlertName: "HighLLMLatency", ruletypes.LabelSeverityName: "warning"},
map[string]string{
"summary": "LLM latency high",
"description": "p99 latency of LLM calls is high",
"related_traces": "https://signoz.example.com/ai-observability/explorer?startTime=1&endTime=2",
},
true,
),
},
input: alertmanagertypes.ExpandRequest{
DefaultTitleTemplate: `{{ .CommonLabels.alertname }}`,
DefaultBodyTemplate: `{{ range .Alerts -}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}}{{ if match "/ai-observability" .Annotations.related_traces -}} View in <{{ .Annotations.related_traces }}|ai traces explorer> {{- else -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end }}{{- end}}
{{ end }}`,
},
wantTitle: "HighLLMLatency",
wantBody: []string{"*RelatedTraces:* View in <https://signoz.example.com/ai-observability/explorer?startTime=1&endTime=2|ai traces explorer>\n "},
wantIsDefaultBody: true,
},
{
// Pod crash loop on multiple pods — body is expanded once per alert
// and joined with "\n\n", with the pod name pulled from labels.

View File

@@ -311,11 +311,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (int, error) {
case ruletypes.AlertTypeTraces, ruletypes.AlertTypeAITraces:
params := r.prepareParamsForTraces(ctx, ts, smpl.Metric)
if len(params) > 0 {
explorerPath := "traces-explorer"
if r.typ == ruletypes.AlertTypeAITraces {
explorerPath = "ai-observability/explorer"
}
link := r.ExternalURL(explorerPath, params)
link := r.ExternalURL(r.typ.TracesExplorerPath(), params)
r.logger.InfoContext(ctx, "adding traces link to annotations", slog.String("annotation.link", link))
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationRelatedTraces, Value: link})
}

View File

@@ -31,7 +31,7 @@ const (
{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})
{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})
{{ end }}{{ if .Annotations.related_traces }}{{ if match "/ai-observability" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
{{ end }}{{ end }}`
)

View File

@@ -29,7 +29,7 @@ const (
{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})
{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})
{{ end }}{{ if .Annotations.related_traces }}{{ if match "/ai-observability" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
{{ end }}{{ end }}`
)

View File

@@ -0,0 +1,27 @@
package ruletypes
import "strings"
const (
DefaultTracesExplorerPath = "traces-explorer"
AIObservabilityPathPrefix = "ai-observability"
AITracesExplorerPath = AIObservabilityPathPrefix + "/explorer"
)
// TracesExplorerPath is the UI path the alert's related_traces link opens.
func (t AlertType) TracesExplorerPath() string {
if t == AlertTypeAITraces {
return AITracesExplorerPath
}
return DefaultTracesExplorerPath
}
// RelatedTracesLabel is the button text notifiers show for a related_traces link.
// Any link under the AI observability section counts, matching the templates'
// `match "/ai-observability"` check.
func RelatedTracesLabel(link string) string {
if strings.Contains(link, "/"+AIObservabilityPathPrefix) {
return "View Related AI Traces"
}
return "View Related Traces"
}