Files
logs/internal/ingest/alert_forward.go
2026-06-26 12:51:50 +08:00

140 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ingest
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"git.apinb.com/ops/logs/internal/config"
)
// AlertReceiveBody 与 alert ReceiveRequest 对齐(含必填 raw_data
type AlertReceiveBody struct {
AlertName string `json:"alert_name"`
Summary string `json:"summary"`
Description string `json:"description"`
SeverityCode string `json:"severity_code"`
Value string `json:"value"`
Threshold string `json:"threshold"`
Labels map[string]string `json:"labels"`
Agent string `json:"agent"`
PolicyID uint `json:"policy_id"`
RawData json.RawMessage `json:"raw_data"`
}
type RawEventIngestBody struct {
SourceType string `json:"source_type"`
ResourceUID string `json:"resource_uid,omitempty"`
EventTime time.Time `json:"event_time"`
Severity string `json:"severity"`
Title string `json:"title"`
Message string `json:"message"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
ParseStatus string `json:"parse_status"`
RawPayload json.RawMessage `json:"raw_payload"`
TraceID string `json:"trace_id,omitempty"`
}
func forwardAlert(body AlertReceiveBody) error {
cfg := config.Spec.AlertForward
if cfg == nil || !cfg.Enabled || cfg.BaseURL == "" {
return nil
}
if len(body.RawData) == 0 {
return fmt.Errorf("raw_data 不能为空")
}
if body.AlertName == "" {
body.AlertName = "日志告警"
}
if body.PolicyID == 0 && cfg.DefaultPolicyID > 0 {
body.PolicyID = cfg.DefaultPolicyID
}
rawEvent := buildRawEventIngestBody(body, "parsed")
raw, err := json.Marshal(rawEvent)
if err != nil {
return err
}
url := cfg.BaseURL + "/Alert/v1/raw-events/ingest"
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if cfg.InternalKey != "" {
req.Header.Set("X-Internal-Key", cfg.InternalKey)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("alert returned HTTP %d", resp.StatusCode)
}
return nil
}
func buildRawEventIngestBody(body AlertReceiveBody, parseStatus string) RawEventIngestBody {
sourceType := rawEventSourceType(body)
if parseStatus == "" {
parseStatus = "parsed"
}
annotations := map[string]string{
"description": body.Description,
"value": body.Value,
"threshold": body.Threshold,
"agent": body.Agent,
}
return RawEventIngestBody{
SourceType: sourceType,
ResourceUID: rawEventResourceUID(body.Labels),
EventTime: time.Now().UTC(),
Severity: body.SeverityCode,
Title: firstNonEmpty(body.AlertName, "日志事件"),
Message: firstNonEmpty(body.Summary, body.Description),
Labels: body.Labels,
Annotations: annotations,
ParseStatus: parseStatus,
RawPayload: body.RawData,
}
}
func rawEventSourceType(body AlertReceiveBody) string {
if body.Labels != nil {
switch strings.TrimSpace(body.Labels["source_subtype"]) {
case "syslog":
return "syslog"
case "snmp_trap":
return "trap"
}
}
switch body.Agent {
case "logs-syslog":
return "syslog"
case "logs-trap":
return "trap"
default:
return "syslog"
}
}
func rawEventResourceUID(labels map[string]string) string {
if labels == nil {
return ""
}
if uid := strings.TrimSpace(labels["resource_uid"]); uid != "" {
return uid
}
category := strings.TrimSpace(labels["resource_category"])
identity := strings.TrimSpace(labels["service_identity"])
if category != "" && identity != "" {
return category + ":" + identity
}
return ""
}