LogEvents Schema
Stratara mandates source-generated [LoggerMessage] for all new logging — no logger.LogInformation(...) direct calls. Every log event has a stable EventId from a known range.
ID range allocation
| Range | Owner |
|---|---|
0 – 99_999 |
Reserved (Microsoft / framework defaults) |
100_000 – 199_999 |
Stratara framework (this repo) |
200_000+ |
Consumer apps (NextPA, HiveWeaver, VeloxRAG, your app) |
Sub-buckets inside the framework's 100_000 range are defined in src/Stratara.Diagnostics/LogEvents.cs. The current allocation:
| Bucket | Subsystem | LogEvents nested class |
|---|---|---|
110_000s |
EventBundle / Outbox dispatch | LogEvents.EventBundle, LogEvents.Outbox |
111_000s |
Bus-Envelope integrity (HMAC) | LogEvents.BusEnvelopeIntegrity |
113_000s |
BusEnvelope startup probes | LogEvents.BusEnvelopeStartupProbe |
120_000s |
Projection runtime | LogEvents.Projection, LogEvents.ChangeSet |
130_000s |
Saga runtime | LogEvents.Saga |
140_000s |
Identity + auth | LogEvents.Identity, LogEvents.Authorization |
150_000s |
Security + encryption | LogEvents.KeyStore, LogEvents.Encryption |
160_000s |
Background tasks + workers | LogEvents.BackgroundTask, LogEvents.Worker |
170_000s |
Event-stream hashing | LogEvents.EventStreamHashing |
Consult src/Stratara.Diagnostics/LogEvents.cs for the authoritative current list — buckets shift as features mature.
Authoring a new log event
- Pick a bucket in
LogEvents.cs. If your subsystem doesn't have one yet, add the nested class with a_baseIdconstant. - Add the constant:
public static class Projection { private const int _baseId = 120_000; public const int ProjectionStarted = _baseId + 1; public const int EventsNotRelevantForProjection = _baseId + 2; // … } - Add the
[LoggerMessage]partial method in the appropriateDiagnostics/Extensions/Logger*Extensions.cs:[LoggerMessage( EventId = LogEvents.Projection.ProjectionStarted, Level = LogLevel.Information, Message = "Projection {ProjectionName} started.")] public static partial void LogProjectionStarted(this ILogger logger, string projectionName);
Logger-extension file naming
| Convention | Example |
|---|---|
One Logger{Subject}Extensions.cs per subsystem |
LoggerProjectionExtensions.cs, LoggerSagaExtensions.cs |
Namespace Stratara.Shared.Diagnostics.Extensions regardless of source package |
All packages' logger extensions live in this single namespace |
Class is public static partial class |
Required by the LoggerMessage source generator |
Parameter-type discipline
[LoggerMessage] source-gen accepts any type, but Stratara's Clean Code rule restricts parameters to simple types (string, Guid, int, DateTimeOffset, enums). For aggregate / collection arguments that would otherwise force expensive formatting at call-time, use a small wrapper struct with ToString() — the formatter calls ToString() lazily, only when the channel is enabled.
Canonical examples in the repo:
Stratara.Shared.Diagnostics.Extensions.DistinctEventTypeNames— wrapsIReadOnlyList<IEvent>.Stratara.Projections.Diagnostics.Extensions.ChangeSetFieldNames— wrapsIReadOnlyList<ChangeDetail>.
What never to do
- ❌
logger.LogInformation("…", arg)— direct logger calls. - ❌
if (logger.IsEnabled(LogLevel.Debug)) { logger.LogXxx(...) }— manual IsEnabled guards. The source-gen formatter checks IsEnabled internally; expensive arguments belong in deferred-formatting wrappers. - ❌ Sharing an
EventIdacross two[LoggerMessage]methods — IDs are unique per code path. - ❌ Repurposing a freed
EventId— once shipped, anEventIdis part of the schema's observable contract.