Skip to content

Commit 39a9422

Browse files
authored
Merge pull request #247 from Vulthil/feature/messaging-observability
Messaging metrics (outbox + inbox) and a clear no-transport startup error
2 parents f5dcdcd + 782a5b8 commit 39a9422

24 files changed

Lines changed: 323 additions & 3 deletions

docs/articles/inbox-pattern.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ builder.Services.AddRelationalInbox<AppDbContext>(o =>
8484

8585
The sweep is registered only when `Retention.Enabled` is set. Choose `RetentionPeriod` comfortably longer than the broker's maximum redelivery delay — a marker removed while a duplicate could still arrive would let that duplicate through. The sweep runs through the registered `IIdempotencyStore` when it implements `IInboxRetentionStore` (the relational and Cosmos EF Core stores do); the relational store deletes set-based with `ExecuteDelete`.
8686

87+
### Metrics
88+
89+
The guard emits metrics on a `Meter` named `"Vulthil.Messaging.Inbox"` (`InboxTelemetry.MeterName`): the counters `vulthil.inbox.processed`, `vulthil.inbox.duplicate_skipped`, and `vulthil.inbox.missing_key`. `AddRelationalInbox`/`AddCosmosInbox` auto-register the meter when `EnableMetrics` is on (the default); for a hand-built `MeterProviderBuilder`, use `metrics.AddVulthilInboxInstrumentation()`.
90+
8791
### Relational store
8892

8993
Expose the inbox set on your `DbContext`, apply the entity configuration, and register the store:

docs/articles/outbox-pattern.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ The relay emits an `ActivitySource` named `"Vulthil.SharedKernel.Outbox"` (expos
7272

7373
`AddOutboxEngine` (called by `EnableOutboxProcessing`) registers the source with OpenTelemetry automatically when `EnableTracing` is on (the default), so the spans reach whatever tracer the application has configured without extra wiring. If you build a `TracerProviderBuilder` yourself, the same registration is available as `tracing.AddVulthilOutboxInstrumentation()` — sugar for `AddSource(Telemetry.ActivitySourceName)`.
7474

75+
The relay also emits **metrics** on a `Meter` named `Telemetry.MeterName` (`"Vulthil.SharedKernel.Outbox"`): the counters `vulthil.outbox.relayed` and `vulthil.outbox.failed`. `AddOutboxEngine` auto-registers the meter with OpenTelemetry when `EnableMetrics` is on (the default); for a hand-built `MeterProviderBuilder`, use `metrics.AddVulthilOutboxInstrumentation()`.
76+
7577
## Retention
7678

7779
Processed and dead-lettered rows remain in the `OutboxMessages` table after relay, so the table grows unbounded unless they are pruned. Opt into a retention sweep — a background service that periodically deletes terminal rows older than a window — by enabling `Retention` on the outbox options:

docs/articles/packages/vulthil-messaging-inbox.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Persistence-agnostic: it defines the `IIdempotencyStore` contract and the filter
2020
- The store owns the transactional unit (the filter hands it the consumer invocation); the consumer keeps calling `SaveChanges` as usual
2121
- Deliveries with no resolvable key are rejected (`MissingIdempotencyKeyException`) unless you opt out
2222
- Prune markers with an opt-in background sweep — enable it on the store registration via `AddRelationalInbox<T>(o => o.Retention.Enabled = true)` (or `AddCosmosInbox<T>`)
23+
- Emits metrics (counters `vulthil.inbox.processed`/`duplicate_skipped`/`missing_key`) on a `Meter` (`InboxTelemetry.MeterName`), auto-registered by `AddRelationalInbox`/`AddCosmosInbox` when `EnableMetrics` is on (manual: `metrics.AddVulthilInboxInstrumentation()`)
2324

2425
## Usage
2526

docs/articles/packages/vulthil-sharedkernel-outbox.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ seam. It has **no EF Core dependency**; the EF implementation lives in
2222
row-locking. `AddOutboxEngine` registers the engine's own internals.
2323
- Relay spans are emitted on the `ActivitySource` `"Vulthil.SharedKernel.Outbox"` (`Telemetry.ActivitySourceName`),
2424
auto-registered with OpenTelemetry by `AddOutboxEngine` (manual: `tracing.AddVulthilOutboxInstrumentation()`).
25+
- Relay metrics (counters `vulthil.outbox.relayed`/`vulthil.outbox.failed`) are emitted on a `Meter` (`Telemetry.MeterName`),
26+
auto-registered by `AddOutboxEngine` when `EnableMetrics` is on (manual: `metrics.AddVulthilOutboxInstrumentation()`).
2527
- Opt into a retention sweep via `EnableOutboxProcessing(o => o.Retention.Enabled = true)` to periodically delete processed and dead-lettered rows
2628
older than a window (relational set-based `ExecuteDelete`; the same sweep covers Cosmos).
2729

src/Vulthil.Messaging.Inbox.Cosmos/CosmosInboxExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ public static IServiceCollection AddCosmosInbox<TContext>(
3434
services.TryAddScoped<IIdempotencyStore, CosmosIdempotencyStore<TContext>>();
3535
services.AddInboxRetention(configure);
3636

37+
var options = new InboxOptions();
38+
configure?.Invoke(options);
39+
if (options.EnableMetrics)
40+
{
41+
services.AddOpenTelemetry().WithMetrics(metrics => metrics.AddVulthilInboxInstrumentation());
42+
}
43+
3744
return services;
3845
}
3946
}

src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" />
14+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
1415
</ItemGroup>
1516

1617
</Project>

src/Vulthil.Messaging.Inbox.Relational/RelationalInboxExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ public static IServiceCollection AddRelationalInbox<TContext>(
3434
services.TryAddScoped<IIdempotencyStore, RelationalIdempotencyStore<TContext>>();
3535
services.AddInboxRetention(configure);
3636

37+
var options = new InboxOptions();
38+
configure?.Invoke(options);
39+
if (options.EnableMetrics)
40+
{
41+
services.AddOpenTelemetry().WithMetrics(metrics => metrics.AddVulthilInboxInstrumentation());
42+
}
43+
3744
return services;
3845
}
3946
}

src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
14+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
1415
</ItemGroup>
1516

1617
</Project>

src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,21 @@ public async Task ConsumeAsync(IMessageContext<TMessage> context, ConsumeDelegat
4242
}
4343

4444
InboxLog.MissingKeyAllowed(_logger, messageType);
45+
InboxTelemetry.MissingKey.Add(1);
4546
await next(context);
4647
return;
4748
}
4849

4950
var processed = await _store.ProcessAsync(key, context, _ => next(context), context.CancellationToken);
5051

51-
if (!processed)
52+
if (processed)
53+
{
54+
InboxTelemetry.Processed.Add(1);
55+
}
56+
else
5257
{
5358
InboxLog.DuplicateSkipped(_logger, messageType, key);
59+
InboxTelemetry.DuplicateSkipped.Add(1);
5460
}
5561
}
5662
}

src/Vulthil.Messaging.Inbox/InboxOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public sealed class InboxOptions
1313
/// </summary>
1414
public bool RejectMessagesWithoutKey { get; set; } = true;
1515

16+
/// <summary>
17+
/// Gets or sets a value indicating whether inbox metrics (the <c>vulthil.inbox.*</c> counters on the
18+
/// <see cref="InboxTelemetry.MeterName"/> meter) are auto-registered with OpenTelemetry. Default: <see langword="true"/>.
19+
/// </summary>
20+
public bool EnableMetrics { get; set; } = true;
21+
1622
/// <summary>
1723
/// Gets the retention sweep configuration. Set <see cref="InboxRetentionOptions.Enabled"/> to periodically
1824
/// delete idempotency markers older than <see cref="InboxRetentionOptions.RetentionPeriod"/>.

0 commit comments

Comments
 (0)