feat(core): add alert notification sinks
This commit is contained in:
1 parent
74ac699796
commit
69a71b82f8
6 files changed
+126
No files matched your search
@@ -0,0 +1,26 @@
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public sealed class BufferedDesktopNotifier : IDesktopNotifier
|
||||
{
|
||||
public readonly record struct Toast(string Title, string Body, AlertSeverity Severity, DateTimeOffset At);
|
||||
|
||||
private readonly object gate = new();
|
||||
private readonly List<Toast> pending = new();
|
||||
|
||||
public Task ShowAsync(string title, string body, AlertSeverity severity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (gate)
|
||||
pending.Add(new Toast(title, body, severity, DateTimeOffset.UtcNow));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Toast> DrainPending()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
var drained = pending.ToArray();
|
||||
pending.Clear();
|
||||
return drained;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public sealed class DesktopAlertSink : IAlertSink
|
||||
{
|
||||
private readonly IDesktopNotifier notifier;
|
||||
|
||||
public DesktopAlertSink(IDesktopNotifier notifier)
|
||||
{
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
public Task NotifyAsync(AlertNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var title = notification.Kind == AlertTransitionKind.Fired
|
||||
? $"Alert: {notification.RuleName}"
|
||||
: $"Resolved: {notification.RuleName}";
|
||||
return notifier.ShowAsync(title, notification.Summary(), notification.Severity, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public interface IAlertSink
|
||||
{
|
||||
Task NotifyAsync(AlertNotification notification, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public interface IDesktopNotifier
|
||||
{
|
||||
Task ShowAsync(string title, string body, AlertSeverity severity, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public sealed class LogAlertSink : IAlertSink
|
||||
{
|
||||
private readonly IAlertLog log;
|
||||
|
||||
public LogAlertSink(IAlertLog log)
|
||||
{
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public Task NotifyAsync(AlertNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
log.Append(new AlertLogEntry(notification.Timestamp, notification.Severity, notification.Summary()));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Vantage.Core.Alerts;
|
||||
|
||||
public sealed class WebhookAlertSink : IAlertSink
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly Uri endpoint;
|
||||
private readonly TimeSpan timeout;
|
||||
|
||||
public WebhookAlertSink(HttpClient httpClient, Uri endpoint, TimeSpan? timeout = null)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.endpoint = endpoint;
|
||||
this.timeout = timeout ?? TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(AlertNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var payload = new WebhookPayload(
|
||||
Status: notification.Kind == AlertTransitionKind.Fired ? "firing" : "resolved",
|
||||
Severity: notification.Severity.ToString().ToLowerInvariant(),
|
||||
Rule: notification.RuleName,
|
||||
Host: notification.HostId,
|
||||
Metric: notification.Metric,
|
||||
Value: notification.Value,
|
||||
Threshold: notification.Threshold,
|
||||
Comparison: notification.ComparisonSymbol,
|
||||
Summary: notification.Summary(),
|
||||
Timestamp: notification.Timestamp.ToUnixTimeMilliseconds());
|
||||
|
||||
using var timeoutCts = new CancellationTokenSource(timeout);
|
||||
using var linked = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||
|
||||
using var response = await httpClient
|
||||
.PostAsJsonAsync(endpoint, payload, linked.Token)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private sealed record WebhookPayload(
|
||||
string Status,
|
||||
string Severity,
|
||||
string Rule,
|
||||
string Host,
|
||||
string Metric,
|
||||
double Value,
|
||||
double Threshold,
|
||||
string Comparison,
|
||||
string Summary,
|
||||
long Timestamp);
|
||||
}
|
||||
Reference in new issue
Block a user