diff --git a/deploy/service-failure-notify.mjs b/deploy/service-failure-notify.mjs index bded842..d49e750 100644 --- a/deploy/service-failure-notify.mjs +++ b/deploy/service-failure-notify.mjs @@ -58,6 +58,21 @@ function redact(value, env) { .slice(0, 1_500); } +/** + * 本次服务启动的短标识,用于让每封故障通知各自成会话(见 subject 处的注释)。 + * + * 优先用 systemd 的 `INVOCATION_ID`(每次启动唯一)。它缺失时退回随机值 —— + * 宁可每次不同,也不要因为拿不到标识而退回"主题相同 → 告警被护栏挡下"。 + */ +function invocationTag(env) { + const invocation = String(env.INVOCATION_ID || '').trim(); + if (invocation) return invocation.replace(/[^A-Za-z0-9]/g, '').slice(0, 8); + return createHash('sha256') + .update(`${Date.now()}:${randomUUID()}`) + .digest('hex') + .slice(0, 8); +} + function relayKeyFor(env, serviceName) { const invocation = String(env.INVOCATION_ID || '').trim(); if (invocation) return `service-failure:${invocation}`; @@ -97,7 +112,23 @@ export function buildFailurePayload({ return { to: String(env.AGENTMAIL_FAILURE_RECIPIENT || DEFAULT_RECIPIENT), - subject: `[${agentName}] 桥服务异常终止`, + /* + * 主题带上本次启动的 invocation 短号。 + * + * 服务端按 `AutoAliasFor(收件人, 主题)` 派生会话别名,所以**主题相同 = 同一条会话**。 + * 崩溃循环下这一点会咬人:多封告警全落进同一条会话,而网关对会话内**连续**的中继 + * 邮件有 `maxRelayHops=5` 的硬上限(防两个 Agent 互相唤醒的正当护栏)→ 第 6 封起 + * 返回 403,报告只能落 spool,等下次 ExecStartPost `--flush` 才补投。 + * + * 实测就是这样:dsh 崩溃循环时的六条告警全部 spooled,而**服务反复崩溃正是最需要 + * 告警送达的时刻**。 + * + * 带 invocation 让每次崩溃各自成会话,因而每次都能投出去 —— 护栏不受影响 + * (它针对的是 agent↔agent 的互相唤醒,不是同一个人收多条故障通知)。 + * 副作用是崩溃循环会产生多条会话而不是一条线程;对"服务在崩"这件事, + * 分开计数比合并成一条更容易发现问题。 + */ + subject: `[${agentName}] 桥服务异常终止 #${invocationTag(env)}`, body: lines.join('\n'), relay: 'summary', relay_key: relayKeyFor(env, service),