优化生产日志体积:访问降噪、压缩清理与退款告警节流

跳过轮询/封面等热路径成功请求,按日 gzip 并保留可配天数,退款 max-retry 汇总告警。
This commit is contained in:
yml2213
2026-07-13 22:02:20 +08:00
parent f51aed1f0f
commit 2efcede800
8 changed files with 361 additions and 41 deletions
+50 -21
View File
@@ -225,14 +225,25 @@ func (j *Job) warnMaxRetryRefunds(ctx context.Context, now time.Time) {
j.logger.Warn("refund retry max count scan failed", zap.Error(err))
return
}
if len(rows) == 0 {
return
}
// 汇总一条告警,避免每 2 分钟对同一批单刷屏;更新 last_retry_at 实现 24h 节流
ids := make([]uint64, 0, len(rows))
orderIDs := make([]uint64, 0, len(rows))
for _, row := range rows {
j.logger.Warn("refund retry reached max count, need manual check",
zap.Uint64("payment_id", row.ID),
zap.Uint64("order_id", row.OrderID),
zap.String("biz_type", row.BizType),
zap.String("status", row.Status),
zap.Int("retry_count", row.RetryCount),
)
ids = append(ids, row.ID)
orderIDs = append(orderIDs, row.OrderID)
}
j.logger.Warn("refund retry reached max count, need manual check",
zap.Int("count", len(rows)),
zap.Uint64s("payment_ids", ids),
zap.Uint64s("order_ids", orderIDs),
)
if err := j.db.WithContext(ctx).Model(&model.PaymentOrder{}).
Where("id IN ?", ids).
Update("last_retry_at", now).Error; err != nil {
j.logger.Warn("refund retry bump last_retry_at failed", zap.Error(err))
}
}
@@ -248,6 +259,8 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
}
missing := 0
rebuilt := 0
var missingOrderIDs []uint64
var rebuildFailOrderIDs []uint64
for _, row := range rows {
var count int64
if err := j.db.WithContext(ctx).Model(&model.PaymentOrder{}).
@@ -264,18 +277,11 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
// 非仲裁孤儿 biz_type 无法从订单状态可靠区分,维持只告警。
if row.SettlementStatus == "arbitrated" {
if j.payments == nil {
j.logger.Warn("auto rebuild arbitration refund skipped, payment repository unavailable",
zap.Uint64("order_id", row.ID),
zap.Int64("refund_amount_cent", row.RefundAmountCent),
)
rebuildFailOrderIDs = append(rebuildFailOrderIDs, row.ID)
continue
}
if _, err := j.payments.StartRefund(ctx, row.ID, row.RefundAmountCent, "arbitration_refund", "仲裁退款补偿补建"); err != nil {
j.logger.Warn("auto rebuild arbitration refund failed, need manual check",
zap.Uint64("order_id", row.ID),
zap.Int64("refund_amount_cent", row.RefundAmountCent),
zap.Error(err),
)
rebuildFailOrderIDs = append(rebuildFailOrderIDs, row.ID)
} else {
rebuilt++
j.logger.Info("auto rebuild arbitration refund submitted",
@@ -285,13 +291,36 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
}
continue
}
missingOrderIDs = append(missingOrderIDs, row.ID)
}
// 汇总告警 + Redis 24h 去重,避免每轮扫描刷屏
if len(missingOrderIDs) > 0 && j.shouldWarn(ctx, "missing_payment", 0) {
j.logger.Warn("refund order missing payment record, need manual check",
zap.Uint64("order_id", row.ID),
zap.String("order_no", row.OrderNo),
zap.String("refund_status", row.RefundStatus),
zap.String("settlement_status", row.SettlementStatus),
zap.Int64("refund_amount_cent", row.RefundAmountCent),
zap.Int("count", len(missingOrderIDs)),
zap.Uint64s("order_ids", missingOrderIDs),
)
}
if len(rebuildFailOrderIDs) > 0 && j.shouldWarn(ctx, "rebuild_fail", 0) {
j.logger.Warn("auto rebuild arbitration refund failed, need manual check",
zap.Int("count", len(rebuildFailOrderIDs)),
zap.Uint64s("order_ids", rebuildFailOrderIDs),
)
}
return missing, rebuilt, nil
}
// shouldWarn 用 Redis 做 24h 节流;无 Redis 时每个 job 周期最多打一次同类汇总(由调用方聚合)。
func (j *Job) shouldWarn(ctx context.Context, kind string, id uint64) bool {
if j.redis == nil {
return true
}
key := fmt.Sprintf("hfb:job:refundretry:warn:%s", kind)
if id > 0 {
key = fmt.Sprintf("%s:%d", key, id)
}
ok, err := j.redis.SetNX(ctx, key, "1", manualWarnThreshold).Result()
if err != nil {
return true
}
return ok
}
@@ -46,6 +46,50 @@ func TestRetryBackoffCapsAtOneHour(t *testing.T) {
}
}
func TestWarnMaxRetryRefundsThrottlesByLastRetryAt(t *testing.T) {
db := setupRefundRetryTestDB(t)
job := New(db, nil, zap.NewNop(), nil)
now := time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC)
old := now.Add(-25 * time.Hour)
row := model.PaymentOrder{
PaymentNo: "PAY202607110001",
OrderID: 9,
OrderNo: "ORD202607110001",
UserID: 2,
Provider: "mock",
ThirdOrderID: "REF202607110001",
BizType: "admin_refund",
Status: "failed",
RetryCount: maxRetryCount,
LastRetryAt: &old,
}
if err := db.Create(&row).Error; err != nil {
t.Fatalf("创建退款单失败: %v", err)
}
job.warnMaxRetryRefunds(t.Context(), now)
var latest model.PaymentOrder
if err := db.First(&latest, row.ID).Error; err != nil {
t.Fatalf("查询退款单失败: %v", err)
}
if latest.LastRetryAt == nil || !latest.LastRetryAt.Equal(now) {
t.Fatalf("LastRetryAt 应更新为 now 以节流,got %v", latest.LastRetryAt)
}
// 立即再扫不应匹配(last_retry_at 未超过 24h
var count int64
if err := db.Model(&model.PaymentOrder{}).
Where("retry_count >= ? AND (last_retry_at IS NULL OR last_retry_at <= ?)",
maxRetryCount, now.Add(-manualWarnThreshold)).
Count(&count).Error; err != nil {
t.Fatalf("count: %v", err)
}
if count != 0 {
t.Fatalf("throttled rows count = %d, want 0", count)
}
}
func TestMarkRetryFailedStopsAtMaxCount(t *testing.T) {
db := setupRefundRetryTestDB(t)
job := New(db, nil, zap.NewNop(), nil)