feat(telemetry): core review fixes (Prometheus+OTLP coexistence, ServiceName validation, null guards) + contract overload note

- Fix #1: Prometheus exporter always wired for metrics; OTLP is additive overlay
  when Exporter == ZbExporter.Otlp so /metrics + MapZbMetrics work in all modes.
- Fix #2: BuildOptions throws ArgumentException when ServiceName is null/whitespace.
- Fix #3: AddZbTelemetry(IHostApplicationBuilder) guard: ThrowIfNull(configure)
  added alongside existing ThrowIfNull(builder).
- Fix #6: Contract doc adds IServiceCollection convenience overload signature.
- Tests: +3 new tests (OtlpExporter still serves /metrics, empty ServiceName throws,
  whitespace ServiceName throws). Total: 7 passed (was 4).
This commit is contained in:
Joseph Doherty
2026-06-01 07:43:47 -04:00
parent 2b856074d5
commit 37fb84f477
3 changed files with 80 additions and 8 deletions
@@ -25,6 +25,7 @@ public static class ZbTelemetryExtensions
Action<ZbTelemetryOptions> configure)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
builder.Services.AddZbTelemetry(BuildOptions(configure));
return builder;
}
@@ -94,20 +95,23 @@ public static class ZbTelemetryExtensions
ArgumentNullException.ThrowIfNull(configure);
var options = new ZbTelemetryOptions();
configure(options);
if (string.IsNullOrWhiteSpace(options.ServiceName))
{
throw new ArgumentException(
"ZbTelemetryOptions.ServiceName is required (e.g. \"otopcua\").",
nameof(configure));
}
return options;
}
private static void ApplyMetricsExporter(MeterProviderBuilder metrics, ZbTelemetryOptions options)
{
switch (options.Exporter)
// Prometheus is always wired so that /metrics and MapZbMetrics() work regardless of
// the exporter setting. OTLP is an additive overlay when explicitly requested.
metrics.AddPrometheusExporter();
if (options.Exporter == ZbExporter.Otlp)
{
case ZbExporter.Otlp:
metrics.AddOtlpExporter(o => ConfigureOtlp(o, options));
break;
case ZbExporter.Prometheus:
default:
metrics.AddPrometheusExporter();
break;
metrics.AddOtlpExporter(o => ConfigureOtlp(o, options));
}
}