diff --git a/alerts.go b/alerts.go index 21bec3b..32c99bc 100644 --- a/alerts.go +++ b/alerts.go @@ -435,9 +435,10 @@ func buildErrorRateAlertRules(client ClientData, domain DomainInfo, errCode, lim if statusField == "upstream_status" { fieldDesc = "ответ от origin" } + circle := "🟡" summary := fmt.Sprintf( - "🔴 %s / %s (SID:%s) — %s %s ошибок трафика держится > %s.", - client.ClientTitle, domain.DomainName, domain.SID, codeName, fieldShort, forDuration, + "%s %s / %s (SID:%s) — %s ошибок > %d%% держится > %s. [%s]", + circle, client.ClientTitle, domain.DomainName, domain.SID, codeName, limitPct, forDuration, fieldShort, ) thresholdAnnotation := fmt.Sprintf(">%d%% для %s (%s)", limitPct, statusField, fieldDesc) fieldInfoAnnotation := fmt.Sprintf("для %s (%s)", statusField, fieldDesc) @@ -468,6 +469,7 @@ func buildErrorRateAlertRules(client ClientData, domain DomainInfo, errCode, lim "threshold": thresholdAnnotation, "threshold_value": thresholdValueAnnotation, "field_info": fieldInfoAnnotation, + "severity": "warning", }, "notification_settings": map[string]string{"receiver": receiver}, "data": []interface{}{ diff --git a/alerts_vl.go b/alerts_vl.go index 488af75..3dff1e1 100644 --- a/alerts_vl.go +++ b/alerts_vl.go @@ -221,9 +221,10 @@ func buildErrorRateAlertRulesVL(client ClientData, domain DomainInfo, errCode, l if statusField == "upstream_status" { fieldDesc = "ответ от origin" } + circle := "🟡" summary := fmt.Sprintf( - "🔴 %s / %s (SID:%s) — %s %s ошибок трафика держится > %s.", - client.ClientTitle, domain.DomainName, domain.SID, codeName, fieldShort, forDuration, + "%s %s / %s (SID:%s) — %s ошибок > %d%% держится > %s. [%s]", + circle, client.ClientTitle, domain.DomainName, domain.SID, codeName, limitPct, forDuration, fieldShort, ) thresholdAnnotation := fmt.Sprintf(">%d%% для %s (%s)", limitPct, statusField, fieldDesc) fieldInfoAnnotation := fmt.Sprintf("для %s (%s)", statusField, fieldDesc) @@ -286,6 +287,7 @@ func buildErrorRateAlertRulesVL(client ClientData, domain DomainInfo, errCode, l "threshold": thresholdAnnotation, "threshold_value": thresholdValueAnnotation, "field_info": fieldInfoAnnotation, + "severity": "warning", }, "notification_settings": map[string]string{"receiver": receiver}, "data": []interface{}{ diff --git a/panels_vl.go b/panels_vl.go index c05d15e..40a9dc3 100644 --- a/panels_vl.go +++ b/panels_vl.go @@ -451,16 +451,37 @@ func buildVLBucketLogsTargets(cfg map[string]interface{}, _ string, _ string, da } } + // Также поддерживаем прямое указание terms_field в query_config + if tf := getString(cfg, "terms_field"); tf != "" { + termsField = tf + } + if termsField == "" { termsField = "_msg" } + // vl_metric: "rps" — делим count на интервал для получения RPS + vlMetric := getString(cfg, "vl_metric") + vlInterval := getString(cfg, "vl_interval") + if vlInterval == "" { + vlInterval = "1m" + } + // LogsQL: группировка по полю + top N var expr string - if termsSize > 0 { - expr = fmt.Sprintf("%s | stats by (%s) count() hits | sort by (hits desc) | limit %d", baseQuery, termsField, termsSize) + if vlMetric == "rps" { + // RPS: stats by (time:interval, field) count() / interval_seconds + if termsSize > 0 { + expr = fmt.Sprintf("%s | stats by (_time:%s, %s) count() hits | sort by (hits desc) | limit %d", baseQuery, vlInterval, termsField, termsSize) + } else { + expr = fmt.Sprintf("%s | stats by (_time:%s, %s) count() hits", baseQuery, vlInterval, termsField) + } } else { - expr = fmt.Sprintf("%s | stats by (%s) count() hits", baseQuery, termsField) + if termsSize > 0 { + expr = fmt.Sprintf("%s | stats by (%s) count() hits | sort by (hits desc) | limit %d", baseQuery, termsField, termsSize) + } else { + expr = fmt.Sprintf("%s | stats by (%s) count() hits", baseQuery, termsField) + } } // legendFormat с именем поля — плагин подставит значение поля группировки @@ -469,6 +490,24 @@ func buildVLBucketLogsTargets(cfg map[string]interface{}, _ string, _ string, da makeVLStatsTarget("A", expr, datasourceUID, legendFormat), } + // Для RPS добавляем math expression $A / 60 (интервал 1m = 60 секунд) + if vlMetric == "rps" { + // Скрываем raw query A, показываем только RPS + // legendFormat задаём на raw target — math expression наследует метки + rawTarget := makeVLStatsTarget("A", expr, datasourceUID, legendFormat) + rawTarget["hide"] = true + mathExpr := map[string]interface{}{ + "refId": "RPS", + "datasource": map[string]interface{}{"type": "__expr__", "uid": "__expr__"}, + "type": "math", + "expression": "$A / 60", + } + targets = []interface{}{ + rawTarget, + mathExpr, + } + } + // Если задан unknown_label — добавляем отдельный target для записей с пустым значением поля unknownLabel := getString(cfg, "unknown_label") if unknownLabel != "" {