From 567dda70c056ab2dc5a507699b9840cd595abb17 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Tue, 21 Jul 2026 17:19:39 +0800 Subject: [PATCH] feat: propagate alert forwarding trace ids --- internal/ingest/alert_forward.go | 23 ++++++++++++++++++++--- internal/ingest/alert_outbox.go | 4 +++- internal/ingest/replay.go | 4 +++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/internal/ingest/alert_forward.go b/internal/ingest/alert_forward.go index c628df4..f6ec8a7 100644 --- a/internal/ingest/alert_forward.go +++ b/internal/ingest/alert_forward.go @@ -2,6 +2,7 @@ package ingest import ( "bytes" + "crypto/sha256" "encoding/hex" "encoding/json" "errors" @@ -14,7 +15,10 @@ import ( "git.apinb.com/ops/logs/internal/config" ) -const alertResponseBodyLimit = 1 << 20 +const ( + alertResponseBodyLimit = 1 << 20 + maxAlertTraceIDLength = 96 +) var errAlertForwardDisabled = errors.New("Alert 转发未启用或 base_url 为空") @@ -36,6 +40,7 @@ type AlertReceiveBody struct { PolicyID uint `json:"policy_id"` State string `json:"state,omitempty"` SourceEventKey string `json:"source_event_key"` + TraceID string `json:"trace_id"` OccurredAt time.Time `json:"occurred_at"` RawData json.RawMessage `json:"raw_data"` } @@ -70,11 +75,12 @@ func forwardAlert(body AlertReceiveBody) error { if body.PolicyID == 0 && cfg.DefaultPolicyID > 0 { body.PolicyID = cfg.DefaultPolicyID } + body.TraceID = ensureAlertTraceID(body.TraceID, body.SourceEventKey) raw, err := json.Marshal(body) if err != nil { return err } - result, err := postAlertPayload(cfg, "/Alert/v1/alerts/receive", raw) + result, err := postAlertPayload(cfg, "/Alert/v1/alerts/receive", raw, body.TraceID) if err != nil { return err } @@ -101,12 +107,13 @@ func forwardAlert(body AlertReceiveBody) error { return nil } -func postAlertPayload(cfg *config.AlertForwardConf, path string, payload []byte) (*alertForwardResponse, error) { +func postAlertPayload(cfg *config.AlertForwardConf, path string, payload []byte, traceID string) (*alertForwardResponse, error) { req, err := http.NewRequest(http.MethodPost, cfg.BaseURL+path, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("创建 Alert 转发请求失败:%w", err) } req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Trace-Id", traceID) if cfg.InternalKey != "" { req.Header.Set("X-Internal-Key", cfg.InternalKey) } @@ -173,9 +180,19 @@ func buildRawEventIngestBody(body AlertReceiveBody, parseStatus string) RawEvent Annotations: annotations, ParseStatus: parseStatus, RawPayload: body.RawData, + TraceID: ensureAlertTraceID(body.TraceID, body.SourceEventKey), } } +func ensureAlertTraceID(traceID, sourceEventKey string) string { + traceID = strings.TrimSpace(traceID) + if traceID != "" && len(traceID) <= maxAlertTraceIDLength { + return traceID + } + sum := sha256.Sum256([]byte(strings.TrimSpace(sourceEventKey))) + return hex.EncodeToString(sum[:16]) +} + func validFingerprint(value string) bool { if len(value) != 64 { return false diff --git a/internal/ingest/alert_outbox.go b/internal/ingest/alert_outbox.go index 21f64d5..f3a659b 100644 --- a/internal/ingest/alert_outbox.go +++ b/internal/ingest/alert_outbox.go @@ -25,6 +25,7 @@ const ( ) func enqueueAlertWithDB(db *gorm.DB, logEventID uint, body AlertReceiveBody) (uint, error) { + body.TraceID = ensureAlertTraceID(body.TraceID, body.SourceEventKey) payload, err := json.Marshal(body) if err != nil { return 0, err @@ -193,11 +194,12 @@ func forwardRawEvent(body RawEventIngestBody) error { if len(body.RawPayload) == 0 { return fmt.Errorf("raw_payload 不能为空") } + body.TraceID = ensureAlertTraceID(body.TraceID, body.SourceEventKey) raw, err := json.Marshal(body) if err != nil { return err } - result, err := postAlertPayload(cfg, "/Alert/v1/raw-events/ingest", raw) + result, err := postAlertPayload(cfg, "/Alert/v1/raw-events/ingest", raw, body.TraceID) if err != nil { return err } diff --git a/internal/ingest/replay.go b/internal/ingest/replay.go index 60d94b7..b22d8e0 100644 --- a/internal/ingest/replay.go +++ b/internal/ingest/replay.go @@ -53,9 +53,10 @@ func BuildReplayRawEventPayload(ev models.LogEvent) (RawEventIngestBody, error) labels["resource_uid"] = resourceUID labels["resource_category"] = category labels["service_identity"] = identity + sourceEventKey := fmt.Sprintf("logs:replay:%d:%d", ev.ID, now.UnixNano()) return RawEventIngestBody{ SourceType: sourceType, - SourceEventKey: fmt.Sprintf("logs:replay:%d:%d", ev.ID, now.UnixNano()), + SourceEventKey: sourceEventKey, State: "firing", ResourceUID: resourceUID, EventTime: now, @@ -69,6 +70,7 @@ func BuildReplayRawEventPayload(ev models.LogEvent) (RawEventIngestBody, error) }, ParseStatus: "parsed", RawPayload: rawBytes, + TraceID: ensureAlertTraceID("", sourceEventKey), }, nil }