perf(audit): bounded async audit writer with batched inserts, one-time bootstrap, retention sweep
This commit is contained in:
+26
-2
@@ -99,16 +99,40 @@ public static class AuthStoreServiceCollectionExtensions
|
||||
|
||||
services.AddSingleton(sp =>
|
||||
new SqliteCanonicalAuditStore(sp.GetRequiredService<AuthSqliteConnectionFactory>()));
|
||||
services.AddSingleton<IAuditEventSink>(sp => sp.GetRequiredService<SqliteCanonicalAuditStore>());
|
||||
// Resolve the logger defensively: the production host always registers ILogger<T>, but the
|
||||
// DI-only auth/CLI/dashboard unit tests build a bare ServiceCollection without AddLogging().
|
||||
// Fall back to NullLogger there so the audit writer (and the IApiKeyAuditStore override that
|
||||
// depends on it) still resolve. The write path is best-effort regardless.
|
||||
services.AddSingleton<IAuditWriter>(sp =>
|
||||
services.AddSingleton(sp =>
|
||||
new CanonicalAuditWriter(
|
||||
sp.GetRequiredService<SqliteCanonicalAuditStore>(),
|
||||
sp.GetRequiredService<IAuditEventSink>(),
|
||||
sp.GetService<ILogger<CanonicalAuditWriter>>()
|
||||
?? Microsoft.Extensions.Logging.Abstractions.NullLogger<CanonicalAuditWriter>.Instance));
|
||||
|
||||
// The registered IAuditWriter is the bounded, asynchronous one: audit producers — above
|
||||
// all IConstraintEnforcer.RecordDenialAsync, which fires once per denied tag inside bulk
|
||||
// RPC loops — enqueue and return instead of awaiting a SQLite insert each. No producer
|
||||
// signature changes; the seam is entirely here. AuditDrainService batches the buffered
|
||||
// events onto the sink, owns the one-time schema bootstrap and sweeps expired rows. Where
|
||||
// no hosted service runs (the `apikey` CLI, the DI-only tests) the channel writer falls
|
||||
// back to CanonicalAuditWriter's synchronous path, so audit is never silently buffered
|
||||
// into a channel nobody drains.
|
||||
services.AddSingleton(sp =>
|
||||
new ChannelAuditWriter(
|
||||
sp.GetRequiredService<CanonicalAuditWriter>(),
|
||||
sp.GetService<ILogger<ChannelAuditWriter>>()
|
||||
?? Microsoft.Extensions.Logging.Abstractions.NullLogger<ChannelAuditWriter>.Instance));
|
||||
services.AddSingleton<IAuditWriter>(sp => sp.GetRequiredService<ChannelAuditWriter>());
|
||||
services.AddSingleton(sp => new AuditDrainService(
|
||||
sp.GetRequiredService<ChannelAuditWriter>(),
|
||||
sp.GetRequiredService<IAuditEventSink>(),
|
||||
security,
|
||||
sp.GetService<TimeProvider>() ?? TimeProvider.System,
|
||||
sp.GetService<ILogger<AuditDrainService>>()
|
||||
?? Microsoft.Extensions.Logging.Abstractions.NullLogger<AuditDrainService>.Instance));
|
||||
services.AddHostedService(sp => sp.GetRequiredService<AuditDrainService>());
|
||||
|
||||
// OVERRIDE the library's IApiKeyAuditStore (AddZbApiKeyAuth registered the library's
|
||||
// SqliteApiKeyAuditStore via TryAddSingleton) with an adapter that canonicalizes every
|
||||
// library-emitted ApiKeyAuditEntry onto AuditEvent and forwards it through IAuditWriter.
|
||||
|
||||
Reference in New Issue
Block a user