consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
29
src/Timeline/StellaOps.TimelineIndexer.Worker/Program.cs
Normal file
29
src/Timeline/StellaOps.TimelineIndexer.Worker/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
using StellaOps.TimelineIndexer.Infrastructure.DependencyInjection;
|
||||
using StellaOps.TimelineIndexer.Infrastructure.Options;
|
||||
using StellaOps.TimelineIndexer.Infrastructure.Subscriptions;
|
||||
using StellaOps.TimelineIndexer.Worker;
|
||||
using StellaOps.Worker.Health;
|
||||
|
||||
var builder = WebApplication.CreateSlimBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddEnvironmentVariables();
|
||||
builder.Configuration.AddEnvironmentVariables(prefix: "TIMELINE_");
|
||||
|
||||
builder.Services.AddTimelineIndexerPostgres(builder.Configuration);
|
||||
builder.Services.AddOptions<TimelineIngestionOptions>()
|
||||
.Bind(builder.Configuration.GetSection("Ingestion"));
|
||||
|
||||
builder.Services.AddSingleton<TimelineEnvelopeParser>();
|
||||
builder.Services.AddSingleton<ITimelineEventSubscriber, NatsTimelineEventSubscriber>();
|
||||
builder.Services.AddSingleton<ITimelineEventSubscriber, RedisTimelineEventSubscriber>();
|
||||
builder.Services.AddSingleton<ITimelineEventSubscriber, NullTimelineEventSubscriber>();
|
||||
builder.Services.AddHostedService<TimelineIngestionWorker>();
|
||||
builder.Services.AddWorkerHealthChecks();
|
||||
|
||||
var app = builder.Build();
|
||||
app.MapWorkerHealthEndpoints();
|
||||
app.Run();
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"StellaOps.TimelineIndexer.Worker": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
|
||||
<UserSecretsId>dotnet-StellaOps.TimelineIndexer.Worker-f6dbdeac-9eb5-4250-9384-ef93fc70f770</UserSecretsId>
|
||||
|
||||
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- FrameworkReference Microsoft.AspNetCore.App is provided by Sdk.Web -->
|
||||
|
||||
|
||||
<!-- Microsoft.Extensions.Hosting is provided by Sdk.Worker -->
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.TimelineIndexer.Core\StellaOps.TimelineIndexer.Core.csproj"/>
|
||||
|
||||
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.TimelineIndexer.Infrastructure\StellaOps.TimelineIndexer.Infrastructure.csproj"/>
|
||||
|
||||
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Worker.Health/StellaOps.Worker.Health.csproj"/>
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
8
src/Timeline/StellaOps.TimelineIndexer.Worker/TASKS.md
Normal file
8
src/Timeline/StellaOps.TimelineIndexer.Worker/TASKS.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# StellaOps.TimelineIndexer.Worker Task Board
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_solid_review.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Worker/StellaOps.TimelineIndexer.Worker.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
using StellaOps.TimelineIndexer.Core.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.TimelineIndexer.Worker;
|
||||
|
||||
/// <summary>
|
||||
/// Background consumer that reads timeline events from configured subscribers and persists them via the ingestion service.
|
||||
/// </summary>
|
||||
public sealed class TimelineIngestionWorker(
|
||||
IEnumerable<ITimelineEventSubscriber> subscribers,
|
||||
ITimelineIngestionService ingestionService,
|
||||
ILogger<TimelineIngestionWorker> logger,
|
||||
TimeProvider? timeProvider = null) : BackgroundService
|
||||
{
|
||||
private static readonly Meter Meter = new("StellaOps.TimelineIndexer", "1.0.0");
|
||||
private static readonly Counter<long> IngestedCounter = Meter.CreateCounter<long>("timeline.ingested");
|
||||
private static readonly Counter<long> DuplicateCounter = Meter.CreateCounter<long>("timeline.duplicates");
|
||||
private static readonly Counter<long> FailedCounter = Meter.CreateCounter<long>("timeline.failed");
|
||||
private static readonly Histogram<double> LagHistogram = Meter.CreateHistogram<double>("timeline.ingest.lag.seconds");
|
||||
|
||||
private readonly IEnumerable<ITimelineEventSubscriber> _subscribers = subscribers;
|
||||
private readonly ITimelineIngestionService _ingestion = ingestionService;
|
||||
private readonly ILogger<TimelineIngestionWorker> _logger = logger;
|
||||
private readonly ConcurrentDictionary<(string tenant, string eventId), byte> _sessionSeen = new();
|
||||
private readonly TimeProvider _timeProvider = timeProvider ?? TimeProvider.System;
|
||||
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var tasks = _subscribers.Select(subscriber => ConsumeAsync(subscriber, stoppingToken)).ToArray();
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async Task ConsumeAsync(ITimelineEventSubscriber subscriber, CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var envelope in subscriber.SubscribeAsync(cancellationToken))
|
||||
{
|
||||
var key = (envelope.TenantId, envelope.EventId);
|
||||
if (!_sessionSeen.TryAdd(key, 0))
|
||||
{
|
||||
DuplicateCounter.Add(1);
|
||||
_logger.LogDebug("Skipped duplicate timeline event {EventId} for tenant {Tenant}", envelope.EventId, envelope.TenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _ingestion.IngestAsync(envelope, cancellationToken).ConfigureAwait(false);
|
||||
if (result.Inserted)
|
||||
{
|
||||
IngestedCounter.Add(1);
|
||||
LagHistogram.Record((_timeProvider.GetUtcNow() - envelope.OccurredAt).TotalSeconds);
|
||||
_logger.LogInformation("Ingested timeline event {EventId} from {Source} (tenant {Tenant})", envelope.EventId, envelope.Source, envelope.TenantId);
|
||||
}
|
||||
else
|
||||
{
|
||||
DuplicateCounter.Add(1);
|
||||
_logger.LogDebug("Store reported duplicate for event {EventId} tenant {Tenant}", envelope.EventId, envelope.TenantId);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Respect shutdown.
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FailedCounter.Add(1);
|
||||
_logger.LogError(ex, "Failed to ingest timeline event {EventId} for tenant {Tenant}", envelope.EventId, envelope.TenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Postgres": {
|
||||
"Timeline": {
|
||||
"ConnectionString": "Host=localhost;Database=timeline;Username=timeline;Password=timeline;",
|
||||
"SchemaName": "timeline",
|
||||
"CommandTimeoutSeconds": 30
|
||||
}
|
||||
},
|
||||
"Ingestion": {
|
||||
"Nats": {
|
||||
"Enabled": false,
|
||||
"Url": "nats://localhost:4222",
|
||||
"Subject": "orch.event",
|
||||
"QueueGroup": "timeline-indexer",
|
||||
"Prefetch": 64
|
||||
},
|
||||
"Redis": {
|
||||
"Enabled": false,
|
||||
"ConnectionString": "localhost:6379",
|
||||
"Stream": "timeline.events",
|
||||
"ConsumerGroup": "timeline-indexer",
|
||||
"ConsumerName": "timeline-worker",
|
||||
"ValueField": "data",
|
||||
"MaxBatchSize": 128,
|
||||
"PollIntervalMilliseconds": 250
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Postgres": {
|
||||
"Timeline": {
|
||||
"ConnectionString": "Host=localhost;Database=timeline;Username=timeline;Password=timeline;",
|
||||
"SchemaName": "timeline",
|
||||
"CommandTimeoutSeconds": 30
|
||||
}
|
||||
},
|
||||
"Ingestion": {
|
||||
"Nats": {
|
||||
"Enabled": false,
|
||||
"Url": "nats://localhost:4222",
|
||||
"Subject": "orch.event",
|
||||
"QueueGroup": "timeline-indexer",
|
||||
"Prefetch": 64
|
||||
},
|
||||
"Redis": {
|
||||
"Enabled": false,
|
||||
"ConnectionString": "localhost:6379",
|
||||
"Stream": "timeline.events",
|
||||
"ConsumerGroup": "timeline-indexer",
|
||||
"ConsumerName": "timeline-worker",
|
||||
"ValueField": "data",
|
||||
"MaxBatchSize": 128,
|
||||
"PollIntervalMilliseconds": 250
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user