Refactor code structure for improved readability and maintainability; removed redundant code blocks and optimized function calls.
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
master
2025-11-20 07:50:52 +02:00
parent 616ec73133
commit 10212d67c0
473 changed files with 316758 additions and 388 deletions

View File

@@ -0,0 +1,16 @@
namespace StellaOps.Notifier.Worker.Options;
public sealed class EgressSloOptions
{
/// <summary>
/// Webhook endpoint to receive SLO delivery signals. When null/empty, publishing is disabled.
/// </summary>
public string? Webhook { get; set; }
/// <summary>
/// Request timeout in seconds for the webhook call.
/// </summary>
public int TimeoutSeconds { get; set; } = 5;
public bool Enabled => !string.IsNullOrWhiteSpace(Webhook);
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.Worker.Processing;
/// <summary>
/// Tracks per-event delivery intents for SLO evaluation and webhook emission.
/// </summary>
internal sealed class EgressSloContext
{
private readonly List<EgressSloSignal> _signals = new();
public IReadOnlyList<EgressSloSignal> Signals => _signals;
public static EgressSloContext FromNotifyEvent(NotifyEvent notifyEvent)
{
ArgumentNullException.ThrowIfNull(notifyEvent);
return new EgressSloContext
{
EventId = notifyEvent.EventId,
TenantId = notifyEvent.Tenant,
EventKind = notifyEvent.Kind,
OccurredAt = notifyEvent.Ts
};
}
public Guid EventId { get; private set; }
public string TenantId { get; private set; } = string.Empty;
public string EventKind { get; private set; } = string.Empty;
public DateTimeOffset OccurredAt { get; private set; }
public void AddDelivery(string channelType, string template, string kind)
{
_signals.Add(new EgressSloSignal(channelType, template, kind, OccurredAt));
}
}
internal sealed record EgressSloSignal(
string Channel,
string Template,
string Kind,
DateTimeOffset OccurredAt);

View File

@@ -0,0 +1,93 @@
using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Options;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.Notifier.Worker.Processing;
internal sealed class HttpEgressSloSink : IEgressSloSink
{
private readonly IHttpClientFactory _clientFactory;
private readonly EgressSloOptions _options;
private readonly ILogger<HttpEgressSloSink> _logger;
public HttpEgressSloSink(
IHttpClientFactory clientFactory,
IOptions<EgressSloOptions> options,
ILogger<HttpEgressSloSink> logger)
{
_clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task PublishAsync(EgressSloContext context, CancellationToken cancellationToken)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (!_options.Enabled)
{
return;
}
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (_options.TimeoutSeconds > 0)
{
linkedCts.CancelAfter(TimeSpan.FromSeconds(_options.TimeoutSeconds));
}
try
{
var client = _clientFactory.CreateClient("notifier-slo-webhook");
var payload = Map(context);
var response = await client.PostAsJsonAsync(_options.Webhook, payload, linkedCts.Token).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning(
"SLO webhook returned non-success status {StatusCode} for event {EventId} (tenant {TenantId}).",
(int)response.StatusCode,
context.EventId,
context.TenantId);
}
}
catch (OperationCanceledException) when (linkedCts.IsCancellationRequested)
{
_logger.LogWarning(
"SLO webhook timed out after {TimeoutSeconds}s for event {EventId} (tenant {TenantId}).",
_options.TimeoutSeconds,
context.EventId,
context.TenantId);
}
catch (Exception ex)
{
_logger.LogWarning(
ex,
"Failed to publish SLO webhook for event {EventId} (tenant {TenantId}).",
context.EventId,
context.TenantId);
}
}
private static object Map(EgressSloContext context)
=> new
{
context.EventId,
context.TenantId,
context.EventKind,
context.OccurredAt,
deliveries = context.Signals.Select(signal => new
{
signal.Channel,
signal.Template,
signal.Kind,
signal.OccurredAt
})
};
}

View File

@@ -0,0 +1,17 @@
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Notifier.Worker.Processing;
internal interface IEgressSloSink
{
Task PublishAsync(EgressSloContext context, CancellationToken cancellationToken);
}
internal sealed class NullEgressSloSink : IEgressSloSink
{
public Task PublishAsync(EgressSloContext context, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}