feat(notify): per-channel delivery results + accurate notification metrics
Dispatcher.Send now returns []ChannelResult{Type, Err} alongside the
aggregated error, and scheduler.checkDomain increments
NotificationsTotal per channel type/status instead of a single
unconditional IncNotification("dispatch", newStatus) placeholder that
ignored per-channel delivery outcome.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
@@ -49,14 +49,23 @@ func NewDispatcher(store ChannelStore, cipher Decryptor) *Dispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelResult is the per-channel delivery outcome, so callers can record
|
||||
// success/failure metrics per channel type instead of one aggregate blob.
|
||||
type ChannelResult struct {
|
||||
Type string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Send delivers ev to every enabled channel of projectID. Errors from
|
||||
// individual channels are aggregated (via errors.Join) rather than aborting
|
||||
// delivery to the remaining channels.
|
||||
func (d *Dispatcher) Send(ctx context.Context, projectID uuid.UUID, ev Event) error {
|
||||
// delivery to the remaining channels; the per-channel outcome is also
|
||||
// returned so callers can record accurate per-channel/status metrics.
|
||||
func (d *Dispatcher) Send(ctx context.Context, projectID uuid.UUID, ev Event) ([]ChannelResult, error) {
|
||||
channels, err := d.store.ListEnabledChannels(ctx, projectID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
var results []ChannelResult
|
||||
var errs []error
|
||||
for _, ch := range channels {
|
||||
n, ok := d.byType[ch.Type]
|
||||
@@ -65,18 +74,21 @@ func (d *Dispatcher) Send(ctx context.Context, projectID uuid.UUID, ev Event) er
|
||||
}
|
||||
secret := ""
|
||||
if ch.SecretEnc != "" {
|
||||
b, err := d.cipher.Decrypt(ch.SecretEnc)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
b, derr := d.cipher.Decrypt(ch.SecretEnc)
|
||||
if derr != nil {
|
||||
errs = append(errs, derr)
|
||||
results = append(results, ChannelResult{Type: ch.Type, Err: derr})
|
||||
continue
|
||||
}
|
||||
secret = string(b)
|
||||
}
|
||||
if err := n.Send(ctx, ch.Config, secret, ev); err != nil {
|
||||
errs = append(errs, err)
|
||||
serr := n.Send(ctx, ch.Config, secret, ev)
|
||||
if serr != nil {
|
||||
errs = append(errs, serr)
|
||||
}
|
||||
results = append(results, ChannelResult{Type: ch.Type, Err: serr})
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
return results, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// SendTest sends a single synthetic Event directly through the Notifier for
|
||||
|
||||
Reference in New Issue
Block a user