using Elsa.Diagnostics.StructuredLogs.Contracts; using Elsa.Diagnostics.StructuredLogs.Extensions; using Elsa.Diagnostics.StructuredLogs.Models; using Elsa.Diagnostics.StructuredLogs.Options; using Elsa.Diagnostics.StructuredLogs.Persistence.Relational.Contracts; using Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite.Extensions; using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Diagnostics.StructuredLogs.Persistence.ConformanceTests; /// /// One InMemory or Sqlite plus the registry and options it reads. /// public sealed class StructuredLogStoreScenario : IAsyncDisposable { private readonly Func _flushAsync; private readonly Func _disposeAsync; private StructuredLogStoreScenario( IStructuredLogStore store, IStructuredLogSourceRegistry sourceRegistry, bool reportsRingDroppedEvents, Func flushAsync, Func disposeAsync) { Store = store; SourceRegistry = sourceRegistry; ReportsRingDroppedEvents = reportsRingDroppedEvents; _flushAsync = flushAsync; _disposeAsync = disposeAsync; } public IStructuredLogStore Store { get; } public IStructuredLogSourceRegistry SourceRegistry { get; } /// /// InMemory reports ring-buffer overflow on . /// Relational QueryAsync stays 0; write-queue drops live on storage diagnostics. /// public bool ReportsRingDroppedEvents { get; } public async ValueTask WriteAsync(params StructuredLogEvent[] events) { await Store.WriteManyAsync(events); await _flushAsync(); } public ValueTask DisposeAsync() => _disposeAsync(); public static Task CreateInMemoryAsync(Action? configure = null) { var services = new ServiceCollection() .AddLogging() .AddStructuredLogsServices(options => { ApplySharedDefaults(options); configure?.Invoke(options); }) .BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); return Task.FromResult(new StructuredLogStoreScenario( services.GetRequiredService(), services.GetRequiredService(), reportsRingDroppedEvents: true, () => ValueTask.CompletedTask, () => services.DisposeAsync())); } public static async Task CreateSqliteAsync(Action? configure = null) { var directory = Path.Join(Path.GetTempPath(), $"elsa-structured-logs-conformance-{Guid.NewGuid():N}"); Directory.CreateDirectory(directory); var databasePath = Path.Join(directory, "structured-logs.db"); ServiceProvider? services = null; try { services = new ServiceCollection() .AddLogging() .AddStructuredLogsServices(options => { ApplySharedDefaults(options); configure?.Invoke(options); }) .AddSqliteStructuredLogPersistence(options => options.ConnectionString = $"Data Source={databasePath}") .BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); await services.GetRequiredService().MigrateAsync(); var buffer = services.GetRequiredService(); return new( services.GetRequiredService(), services.GetRequiredService(), reportsRingDroppedEvents: false, () => buffer.FlushAsync(), async () => { await services.DisposeAsync(); SqliteConnection.ClearAllPools(); if (Directory.Exists(directory)) Directory.Delete(directory, true); }); } catch { if (services is not null) await services.DisposeAsync(); SqliteConnection.ClearAllPools(); if (Directory.Exists(directory)) Directory.Delete(directory, true); throw; } } private static void ApplySharedDefaults(StructuredLogsOptions options) { options.RecentLogCapacity = 50; options.MaxRecentLogQuerySize = 25; options.SourceHeartbeatTimeout = TimeSpan.FromSeconds(30); } }