(ctx context.Context, d *database.DB, tenantHost string, ev Event)
| 84 | } |
| 85 | |
| 86 | func (e *Emitter) emit(ctx context.Context, d *database.DB, tenantHost string, ev Event) { |
| 87 | routes, err := d.Queries.ListNotificationRoutesForEvent(ctx, ev.Type) |
| 88 | if err != nil { |
| 89 | if e.log != nil { |
| 90 | e.log.Debug("notifications: list routes failed", "error", err) |
| 91 | } |
| 92 | return |
| 93 | } |
| 94 | if len(routes) == 0 { |
| 95 | return |
| 96 | } |
| 97 | |
| 98 | // Check alert_config: if the event type is explicitly disabled, skip all notifications. |
| 99 | var alertDelay time.Duration |
| 100 | if cfg, err := d.Queries.GetAlertConfigByType(ctx, ev.Type); err == nil { |
| 101 | if !cfg.IsEnabled { |
| 102 | if e.log != nil { |
| 103 | e.log.Debug("notifications: event type disabled in alert lifecycle", "event_type", ev.Type) |
| 104 | } |
| 105 | return |
| 106 | } |
| 107 | if cfg.AlertDelaySeconds != nil && *cfg.AlertDelaySeconds > 0 { |
| 108 | alertDelay = time.Duration(*cfg.AlertDelaySeconds) * time.Second |
| 109 | } |
| 110 | } |
| 111 | // If no config row exists for this event type, allow it through (backwards compat). |
| 112 | |
| 113 | // Inject app_link into metadata so formatters can render clickable links. |
| 114 | ev.Metadata = e.injectAppLink(ctx, d, ev) |
| 115 | |
| 116 | // If this event cancels a delayed counterpart (e.g. host_recovered cancels host_down), |
| 117 | // set a cancel key so the delayed notification is suppressed when it processes. |
| 118 | if e.rdb != nil { |
| 119 | if cancel := cancelKeyForEvent(ev); cancel != "" { |
| 120 | ttl := 10 * time.Minute // keep cancel key long enough to outlive any delay |
| 121 | _ = e.rdb.Set(ctx, tenantRedisKey(tenantHost, cancel), "1", ttl).Err() |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | evSev := SeverityRank(ev.Severity) |
| 126 | for _, row := range routes { |
| 127 | if !row.DestinationEnabled { |
| 128 | continue |
| 129 | } |
| 130 | if SeverityRank(row.MinSeverity) > evSev { |
| 131 | continue |
| 132 | } |
| 133 | // Host group filtering: if route specifies host_group_ids, the event's host must be in at least one. |
| 134 | if hgIDs := parseJSONStringArray(row.HostGroupIds); len(hgIDs) > 0 { |
| 135 | hostID := metadataString(ev.Metadata, "host_id") |
| 136 | if hostID == "" { |
| 137 | continue |
| 138 | } |
| 139 | inAny := false |
| 140 | for _, gid := range hgIDs { |
| 141 | ok, err := d.Queries.HostInHostGroup(ctx, db.HostInHostGroupParams{ |
| 142 | HostID: hostID, |
| 143 | HostGroupID: gid, |
no test coverage detected