mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-13 13:40:41 +01:00
Compare commits
12 Commits
nv/context
...
issue_6021
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b26ab83206 | ||
|
|
56e6c8e386 | ||
|
|
608e44069d | ||
|
|
4fd8bab355 | ||
|
|
3180a5c856 | ||
|
|
9d07444cef | ||
|
|
f42a1af260 | ||
|
|
be8147431d | ||
|
|
57f9774fb1 | ||
|
|
b7abfba3e1 | ||
|
|
8004b62493 | ||
|
|
631e6eefde |
@@ -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 "^https?://[^?#]*/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 "^https?://[^?#]*/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 "^https?://[^?#]*/ai-observability/" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
|
||||
|
||||
{{ end }}{{ end }}`,
|
||||
};
|
||||
|
||||
@@ -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 "^https?://[^?#]*/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 }}`;
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -214,3 +214,28 @@ func TestNotifyTruncatesLongDescription(t *testing.T) {
|
||||
assert.LessOrEqual(t, len(desc), maxDescriptionLenRunes)
|
||||
assert.Equal(t, '…', desc[len(desc)-1])
|
||||
}
|
||||
|
||||
func TestNotifyRelatedTracesLabel(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
link string
|
||||
want string
|
||||
}{
|
||||
{name: "TracesExplorer", link: "https://signoz.example/traces-explorer?q=1", want: "[View related traces]"},
|
||||
{name: "AITracesExplorer", link: "https://signoz.example/ai-observability/explorer?q=1", want: "[View related AI traces]"},
|
||||
{name: "PrefixedInstall", link: "https://signoz.example/signoz/ai-observability/explorer?q=1", want: "[View related AI traces]"},
|
||||
{name: "AIObservabilityHost", link: "https://ai-observability.acme.com/traces-explorer", want: "[View related traces]"},
|
||||
{name: "AIObservabilityInQuery", link: "https://signoz.example/traces-explorer?q=/ai-observability/", want: "[View related traces]"},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
a := alert(true)
|
||||
a.Annotations["related_traces"] = model.LabelValue(testCase.link)
|
||||
_, err := newNotifier(t, m).Notify(ctx(), a)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, m.lastEvent(t).Description, testCase.want+"("+testCase.link+")")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package jsmops
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertemplate"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -39,3 +47,37 @@ func TestToOpsGenieConfigNilHTTPConfig(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, og.HTTPConfig)
|
||||
}
|
||||
|
||||
func TestDefaultDescriptionRelatedTracesLabel(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
link string
|
||||
want string
|
||||
}{
|
||||
{name: "TracesExplorer", link: "https://signoz.example/traces-explorer?q=1", want: "[View related traces]"},
|
||||
{name: "AITracesExplorer", link: "https://signoz.example/ai-observability/explorer?q=1", want: "[View related AI traces]"},
|
||||
{name: "PrefixedInstall", link: "https://signoz.example/signoz/ai-observability/explorer?q=1", want: "[View related AI traces]"},
|
||||
{name: "AIObservabilityHost", link: "https://ai-observability.acme.com/traces-explorer", want: "[View related traces]"},
|
||||
{name: "AIObservabilityInQuery", link: "https://signoz.example/traces-explorer?q=/ai-observability/", want: "[View related traces]"},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
tmpl := test.CreateTmpl(t)
|
||||
templater := alertmanagertemplate.New(tmpl, slog.New(slog.DiscardHandler))
|
||||
a := &types.Alert{Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "HighCPU"},
|
||||
Annotations: model.LabelSet{"related_traces": model.LabelValue(testCase.link)},
|
||||
StartsAt: time.Now().Add(-time.Minute),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
}}
|
||||
|
||||
result, err := templater.Expand(context.Background(), alertmanagertypes.ExpandRequest{
|
||||
DefaultTitleTemplate: alertmanagertypes.DefaultJSMOpsMessageTemplate,
|
||||
DefaultBodyTemplate: alertmanagertypes.DefaultJSMOpsDescriptionTemplate,
|
||||
}, []*types.Alert{a})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, strings.Join(result.Body, ""), testCase.want+"("+testCase.link+")")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 "^https?://[^?#]*/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 "^https?://[^?#]*/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.
|
||||
|
||||
@@ -768,6 +768,7 @@ func TestBaseRule_ExternalURL(t *testing.T) {
|
||||
{name: "default value returned as-is", externalURL: mustParseURL(t, "http://localhost:8080"), want: "http://localhost:8080"},
|
||||
{name: "configured https host", externalURL: mustParseURL(t, "https://signoz.example.com"), want: "https://signoz.example.com"},
|
||||
{name: "configured host with port", externalURL: mustParseURL(t, "http://signoz.internal:3301"), want: "http://signoz.internal:3301"},
|
||||
{name: "configured host with path prefix", externalURL: mustParseURL(t, "https://signoz.example.com/signoz/"), want: "https://signoz.example.com/signoz"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -91,7 +91,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 "^https?://[^?#]*/ai-observability/" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
|
||||
|
||||
{{ end }}{{ end }}`
|
||||
)
|
||||
|
||||
@@ -96,7 +96,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 "^https?://[^?#]*/ai-observability/" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})
|
||||
|
||||
{{ end }}{{ end }}`
|
||||
)
|
||||
|
||||
31
pkg/types/ruletypes/related_links.go
Normal file
31
pkg/types/ruletypes/related_links.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"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 whose path contains the AI observability section counts, so installs
|
||||
// served under a URL prefix still match. Mirrors the templates'
|
||||
// `match "^https?://[^?#]*/ai-observability/"` check.
|
||||
func RelatedTracesLabel(link string) string {
|
||||
if u, err := url.Parse(link); err == nil && strings.Contains(u.Path, "/"+AIObservabilityPathPrefix+"/") {
|
||||
return "View Related AI Traces"
|
||||
}
|
||||
return "View Related Traces"
|
||||
}
|
||||
47
pkg/types/ruletypes/related_links_test.go
Normal file
47
pkg/types/ruletypes/related_links_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRelatedTracesLabel(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
link string
|
||||
want string
|
||||
}{
|
||||
{name: "TracesExplorer", link: "https://signoz.example/traces-explorer?q=1", want: "View Related Traces"},
|
||||
{name: "AITracesExplorer", link: "https://signoz.example/ai-observability/explorer?q=1", want: "View Related AI Traces"},
|
||||
{name: "Empty", link: "", want: "View Related Traces"},
|
||||
{name: "AIObservabilityHost", link: "https://ai-observability.acme.com/traces-explorer", want: "View Related Traces"},
|
||||
{name: "AIObservabilityPathLookalike", link: "https://acme.com/ai-observability-team/explorer", want: "View Related Traces"},
|
||||
{name: "PrefixedInstall", link: "https://acme.com/signoz/ai-observability/explorer?q=1", want: "View Related AI Traces"},
|
||||
{name: "AIObservabilityInQuery", link: "https://acme.com/traces-explorer?q=/ai-observability/", want: "View Related Traces"},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.want, RelatedTracesLabel(testCase.link))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracesExplorerPath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
alertType AlertType
|
||||
want string
|
||||
}{
|
||||
{name: "Metric", alertType: AlertTypeMetric, want: DefaultTracesExplorerPath},
|
||||
{name: "Traces", alertType: AlertTypeTraces, want: DefaultTracesExplorerPath},
|
||||
{name: "Logs", alertType: AlertTypeLogs, want: DefaultTracesExplorerPath},
|
||||
{name: "Exceptions", alertType: AlertTypeExceptions, want: DefaultTracesExplorerPath},
|
||||
{name: "AITraces", alertType: AlertTypeAITraces, want: AITracesExplorerPath},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.want, testCase.alertType.TracesExplorerPath())
|
||||
})
|
||||
}
|
||||
}
|
||||
2
tests/fixtures/notification_channel.py
vendored
2
tests/fixtures/notification_channel.py
vendored
@@ -101,7 +101,7 @@ slack_default_config = {
|
||||
{
|
||||
"api_url": "services/TEAM_ID/BOT_ID/TOKEN_ID", # base_url configured on runtime
|
||||
"title": '[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n {{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n {{" "}}(\n {{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}="{{ $label.Value -}}"\n {{- end }}\n {{- end -}}\n )\n {{- end }}',
|
||||
"text": '{{ range .Alerts -}}\r\n *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}\r\n\r\n *Summary:* {{ .Annotations.summary }}\r\n *Description:* {{ .Annotations.description }}\r\n *RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}\r\n *RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}\r\n\r\n *Details:*\r\n {{ range .Labels.SortedPairs -}}\r\n {{- if ne .Name "ruleId" -}}\r\n \u2022 *{{ .Name }}:* {{ .Value }}\r\n {{ end -}}\r\n {{ end -}}\r\n{{ end }}',
|
||||
"text": '{{ range .Alerts -}}\r\n *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}\r\n\r\n *Summary:* {{ .Annotations.summary }}\r\n *Description:* {{ .Annotations.description }}\r\n *RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}\r\n *RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}}{{ if match "^https?://[^?#]*/ai-observability/" .Annotations.related_traces -}} View in <{{ .Annotations.related_traces }}|ai traces explorer> {{- else -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end }}{{- end}}\r\n\r\n *Details:*\r\n {{ range .Labels.SortedPairs -}}\r\n {{- if ne .Name "ruleId" -}}\r\n \u2022 *{{ .Name }}:* {{ .Value }}\r\n {{ end -}}\r\n {{ end -}}\r\n{{ end }}',
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -62,6 +62,39 @@ NOTIFIERS_TEST = [
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="slack_notifier_ai_traces_default_templating",
|
||||
rule_path="ruler/test_scenarios/rule_state_history_ai_traces/rule.json",
|
||||
alert_data=[
|
||||
types.AlertData(
|
||||
type="traces",
|
||||
data_path="ruler/test_scenarios/rule_state_history_ai_traces/alert_data.jsonl",
|
||||
),
|
||||
],
|
||||
channel_config=slack_default_config,
|
||||
notification_expectation=types.AMNotificationExpectation(
|
||||
should_notify=True,
|
||||
wait_time_seconds=120,
|
||||
notification_validations=[
|
||||
types.NotificationValidation(
|
||||
destination_type="webhook",
|
||||
validation_data={
|
||||
"path": "/services/TEAM_ID/BOT_ID/TOKEN_ID",
|
||||
"json_body": {
|
||||
"username": "Alertmanager",
|
||||
"attachments": [
|
||||
{
|
||||
"color": "danger",
|
||||
"mrkdwn_in": ["fallback", "pretext", "text"],
|
||||
"text": re.compile(r"\*RelatedTraces:\* View in <[^>]*/ai-observability/explorer\?[^>]*\|ai traces explorer>"),
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="msteams_notifier_default_templating",
|
||||
rule_path="ruler/test_scenarios/threshold_above_at_least_once/rule.json",
|
||||
|
||||
@@ -421,7 +421,7 @@ def test_create_channel( # pylint: disable=too-many-arguments,too-many-position
|
||||
"\n"
|
||||
"{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})\n"
|
||||
"\n"
|
||||
"{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})\n"
|
||||
'{{ end }}{{ if .Annotations.related_traces }}{{ if match "^https?://[^?#]*/ai-observability/" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})\n'
|
||||
"\n"
|
||||
"{{ end }}{{ end }}"
|
||||
),
|
||||
@@ -452,7 +452,7 @@ def test_create_channel( # pylint: disable=too-many-arguments,too-many-position
|
||||
"\n"
|
||||
"{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})\n"
|
||||
"\n"
|
||||
"{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})\n"
|
||||
'{{ end }}{{ if .Annotations.related_traces }}{{ if match "^https?://[^?#]*/ai-observability/" .Annotations.related_traces }}[View related AI traces]{{ else }}[View related traces]{{ end }}({{ .Annotations.related_traces }})\n'
|
||||
"\n"
|
||||
"{{ end }}{{ end }}"
|
||||
),
|
||||
|
||||
@@ -29,6 +29,8 @@ LOGS_DATA = "ruler/test_scenarios/threshold_below_at_least_once/alert_data.jsonl
|
||||
LOGS_RULE = "ruler/test_scenarios/threshold_below_at_least_once/rule.json"
|
||||
TRACES_DATA = "ruler/test_scenarios/threshold_above_average/alert_data.jsonl"
|
||||
TRACES_RULE = "ruler/test_scenarios/threshold_above_average/rule.json"
|
||||
AI_TRACES_DATA = "ruler/test_scenarios/rule_state_history_ai_traces/alert_data.jsonl"
|
||||
AI_TRACES_RULE = "ruler/test_scenarios/rule_state_history_ai_traces/rule.json"
|
||||
|
||||
|
||||
GOOGLECHAT_CASES = [
|
||||
@@ -98,6 +100,29 @@ GOOGLECHAT_CASES = [
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="googlechat_rich_card_ai_traces",
|
||||
rule_path=AI_TRACES_RULE,
|
||||
alert_data=[types.AlertData(type="traces", data_path=AI_TRACES_DATA)],
|
||||
channel_config=googlechat_config("gc-ai-traces"),
|
||||
notification_expectation=types.AMNotificationExpectation(
|
||||
should_notify=True,
|
||||
wait_time_seconds=60,
|
||||
notification_validations=[
|
||||
types.NotificationValidation(
|
||||
destination_type="webhook",
|
||||
validation_data={
|
||||
"path": "/v1/spaces/gc-ai-traces/messages",
|
||||
"count": 1,
|
||||
"json_body": googlechat_card_subset(
|
||||
"rule_state_history_ai_traces",
|
||||
[("View Related AI Traces", r"/ai-observability/explorer\?"), ("Open in SigNoz", r"/alerts/overview\?ruleId=")],
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ METRICS_DATA = "ruler/test_scenarios/threshold_above_at_least_once/alert_data.js
|
||||
METRICS_RULE = "ruler/test_scenarios/threshold_above_at_least_once/rule.json"
|
||||
LOGS_DATA = "ruler/test_scenarios/threshold_below_at_least_once/alert_data.jsonl"
|
||||
LOGS_RULE = "ruler/test_scenarios/threshold_below_at_least_once/rule.json"
|
||||
AI_TRACES_DATA = "ruler/test_scenarios/rule_state_history_ai_traces/alert_data.jsonl"
|
||||
AI_TRACES_RULE = "ruler/test_scenarios/rule_state_history_ai_traces/rule.json"
|
||||
|
||||
|
||||
INCIDENTIO_CASES = [
|
||||
@@ -74,6 +76,29 @@ INCIDENTIO_CASES = [
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="incidentio_rich_event_ai_traces",
|
||||
rule_path=AI_TRACES_RULE,
|
||||
alert_data=[types.AlertData(type="traces", data_path=AI_TRACES_DATA)],
|
||||
channel_config=incidentio_config("inc-ai-traces"),
|
||||
notification_expectation=types.AMNotificationExpectation(
|
||||
should_notify=True,
|
||||
wait_time_seconds=60,
|
||||
notification_validations=[
|
||||
types.NotificationValidation(
|
||||
destination_type="webhook",
|
||||
validation_data={
|
||||
"path": incidentio_path("inc-ai-traces"),
|
||||
"count": 1,
|
||||
"json_body": incidentio_event_subset(
|
||||
"rule_state_history_ai_traces",
|
||||
[("View in SigNoz", r"/alerts/overview\?ruleId="), ("View related AI traces", r"/ai-observability/explorer\?")],
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ METRICS_DATA = "ruler/test_scenarios/threshold_above_at_least_once/alert_data.js
|
||||
METRICS_RULE = "ruler/test_scenarios/threshold_above_at_least_once/rule.json"
|
||||
LOGS_DATA = "ruler/test_scenarios/threshold_below_at_least_once/alert_data.jsonl"
|
||||
LOGS_RULE = "ruler/test_scenarios/threshold_below_at_least_once/rule.json"
|
||||
AI_TRACES_DATA = "ruler/test_scenarios/rule_state_history_ai_traces/alert_data.jsonl"
|
||||
AI_TRACES_RULE = "ruler/test_scenarios/rule_state_history_ai_traces/rule.json"
|
||||
|
||||
|
||||
JIRA_CASES = [
|
||||
@@ -79,6 +81,29 @@ JIRA_CASES = [
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="jira_rich_issue_ai_traces",
|
||||
rule_path=AI_TRACES_RULE,
|
||||
alert_data=[types.AlertData(type="traces", data_path=AI_TRACES_DATA)],
|
||||
channel_config=jira_config(),
|
||||
notification_expectation=types.AMNotificationExpectation(
|
||||
should_notify=True,
|
||||
wait_time_seconds=60,
|
||||
notification_validations=[
|
||||
types.NotificationValidation(
|
||||
destination_type="webhook",
|
||||
validation_data={
|
||||
"path": f"{JIRA_API_BASE}/issue",
|
||||
"count": 1,
|
||||
"json_body": jira_issue_subset(
|
||||
"rule_state_history_ai_traces",
|
||||
[("Open in SigNoz", r"/alerts/overview\?ruleId="), ("View Related AI Traces", r"/ai-observability/explorer\?")],
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ METRICS_DATA = "ruler/test_scenarios/threshold_above_at_least_once/alert_data.js
|
||||
METRICS_RULE = "ruler/test_scenarios/threshold_above_at_least_once/rule.json"
|
||||
LOGS_DATA = "ruler/test_scenarios/threshold_below_at_least_once/alert_data.jsonl"
|
||||
LOGS_RULE = "ruler/test_scenarios/threshold_below_at_least_once/rule.json"
|
||||
AI_TRACES_DATA = "ruler/test_scenarios/rule_state_history_ai_traces/alert_data.jsonl"
|
||||
AI_TRACES_RULE = "ruler/test_scenarios/rule_state_history_ai_traces/rule.json"
|
||||
|
||||
|
||||
JSMOPS_CASES = [
|
||||
@@ -81,6 +83,29 @@ JSMOPS_CASES = [
|
||||
],
|
||||
),
|
||||
),
|
||||
types.AlertManagerNotificationTestCase(
|
||||
name="jsmops_rich_alert_ai_traces",
|
||||
rule_path=AI_TRACES_RULE,
|
||||
alert_data=[types.AlertData(type="traces", data_path=AI_TRACES_DATA)],
|
||||
channel_config=jsmops_config(),
|
||||
notification_expectation=types.AMNotificationExpectation(
|
||||
should_notify=True,
|
||||
wait_time_seconds=60,
|
||||
notification_validations=[
|
||||
types.NotificationValidation(
|
||||
destination_type="webhook",
|
||||
validation_data={
|
||||
"path": f"{JSMOPS_API_BASE}/v2/alerts",
|
||||
"count": 1,
|
||||
"json_body": jsmops_alert_subset(
|
||||
"rule_state_history_ai_traces",
|
||||
[("View in SigNoz", r"/alerts/overview\?ruleId="), ("View related AI traces", r"/ai-observability/explorer\?")],
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user