diff --git a/src/Components/Components/src/ComponentsMetrics.cs b/src/Components/Components/src/ComponentsMetrics.cs index d75cb9ea4feb..64fad76c66dd 100644 --- a/src/Components/Components/src/ComponentsMetrics.cs +++ b/src/Components/Components/src/ComponentsMetrics.cs @@ -69,23 +69,19 @@ public ComponentsMetrics(IMeterFactory meterFactory) public void Navigation(string componentType, string route) { - var tags = new TagList - { - { "aspnetcore.components.type", componentType ?? "unknown" }, - { "aspnetcore.components.route", route ?? "unknown" }, - }; + var tags = new TagList(); + AddComponentTypeTag(ref tags, componentType); + AddRouteTag(ref tags, route); _navigationCount.Add(1, tags); } public async Task CaptureEventDuration(Task task, long startTimestamp, string? componentType, string? methodName, string? attributeName) { - var tags = new TagList - { - { "aspnetcore.components.type", componentType ?? "unknown" }, - { "code.function.name", methodName ?? "unknown" }, - { "aspnetcore.components.attribute.name", attributeName ?? "unknown" } - }; + var tags = new TagList(); + AddComponentTypeTag(ref tags, componentType); + AddMethodNameTag(ref tags, methodName); + AddAttributeNameTag(ref tags, attributeName); try { @@ -93,7 +89,7 @@ public async Task CaptureEventDuration(Task task, long startTimestamp, string? c } catch (Exception ex) { - tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + AddErrorTag(ref tags, ex); } var duration = Stopwatch.GetElapsedTime(startTimestamp); _eventDuration.Record(duration.TotalSeconds, tags); @@ -101,23 +97,20 @@ public async Task CaptureEventDuration(Task task, long startTimestamp, string? c public void FailEventSync(Exception ex, long startTimestamp, string? componentType, string? methodName, string? attributeName) { - var tags = new TagList - { - { "aspnetcore.components.type", componentType ?? "unknown" }, - { "code.function.name", methodName ?? "unknown" }, - { "aspnetcore.components.attribute.name", attributeName ?? "unknown" }, - { "error.type", ex.GetType().FullName ?? "unknown" } - }; + var tags = new TagList(); + AddComponentTypeTag(ref tags, componentType); + AddMethodNameTag(ref tags, methodName); + AddAttributeNameTag(ref tags, attributeName); + AddErrorTag(ref tags, ex); + var duration = Stopwatch.GetElapsedTime(startTimestamp); _eventDuration.Record(duration.TotalSeconds, tags); } public async Task CaptureParametersDuration(Task task, long startTimestamp, string? componentType) { - var tags = new TagList - { - { "aspnetcore.components.type", componentType ?? "unknown" }, - }; + var tags = new TagList(); + AddComponentTypeTag(ref tags, componentType); try { @@ -125,7 +118,7 @@ public async Task CaptureParametersDuration(Task task, long startTimestamp, stri } catch(Exception ex) { - tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + AddErrorTag(ref tags, ex); } var duration = Stopwatch.GetElapsedTime(startTimestamp); _parametersDuration.Record(duration.TotalSeconds, tags); @@ -134,11 +127,10 @@ public async Task CaptureParametersDuration(Task task, long startTimestamp, stri public void FailParametersSync(Exception ex, long startTimestamp, string? componentType) { var duration = Stopwatch.GetElapsedTime(startTimestamp); - var tags = new TagList - { - { "aspnetcore.components.type", componentType ?? "unknown" }, - { "error.type", ex.GetType().FullName ?? "unknown" } - }; + var tags = new TagList(); + AddComponentTypeTag(ref tags, componentType); + AddErrorTag(ref tags, ex); + _parametersDuration.Record(duration.TotalSeconds, tags); } @@ -152,7 +144,7 @@ public async Task CaptureBatchDuration(Task task, long startTimestamp, int diffL } catch (Exception ex) { - tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + AddErrorTag(ref tags, ex); } var duration = Stopwatch.GetElapsedTime(startTimestamp); _batchDuration.Record(duration.TotalSeconds, tags); @@ -162,10 +154,9 @@ public async Task CaptureBatchDuration(Task task, long startTimestamp, int diffL public void FailBatchSync(Exception ex, long startTimestamp) { var duration = Stopwatch.GetElapsedTime(startTimestamp); - var tags = new TagList - { - { "error.type", ex.GetType().FullName ?? "unknown" } - }; + var tags = new TagList(); + AddErrorTag(ref tags, ex); + _batchDuration.Record(duration.TotalSeconds, tags); } @@ -174,4 +165,45 @@ public void Dispose() _meter.Dispose(); _lifeCycleMeter.Dispose(); } + + private static void AddComponentTypeTag(ref TagList tags, string? componentType) + { + if (componentType != null) + { + tags.Add("aspnetcore.components.type", componentType); + } + } + + private static void AddRouteTag(ref TagList tags, string? route) + { + if (route != null) + { + tags.Add("aspnetcore.components.route", route); + } + } + + private static void AddMethodNameTag(ref TagList tags, string? methodName) + { + if (methodName != null) + { + tags.Add("code.function.name", methodName); + } + } + + private static void AddAttributeNameTag(ref TagList tags, string? attributeName) + { + if (attributeName != null) + { + tags.Add("aspnetcore.components.attribute.name", attributeName); + } + } + + private static void AddErrorTag(ref TagList tags, Exception? exception) + { + var errorType = exception?.GetType().FullName; + if (errorType is not null) + { + tags.Add("error.type", errorType); + } + } } diff --git a/src/Components/Components/test/ComponentsMetricsTest.cs b/src/Components/Components/test/ComponentsMetricsTest.cs index 388f83bd95a8..195b2d316efa 100644 --- a/src/Components/Components/test/ComponentsMetricsTest.cs +++ b/src/Components/Components/test/ComponentsMetricsTest.cs @@ -400,4 +400,134 @@ public void Dispose_DisposesAllMeters() // Assert - MeterFactory.Create was called twice in constructor Assert.Equal(2, _meterFactory.Meters.Count); } + + [Fact] + public void Navigation_WithNullValues_OmitsTags() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var navigationCounter = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.navigate"); + + // Act + componentsMetrics.Navigation(null, null); + + // Assert + var measurements = navigationCounter.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.Equal(1, measurements[0].Value); + Assert.DoesNotContain("aspnetcore.components.type", measurements[0].Tags); + Assert.DoesNotContain("aspnetcore.components.route", measurements[0].Tags); + } + + [Fact] + public async Task CaptureEventDuration_WithNullValues_OmitsTags() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.handle_event.duration"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await componentsMetrics.CaptureEventDuration(Task.CompletedTask, startTimestamp, null, null, null); + + // Assert + var measurements = eventDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value >= 0); + Assert.DoesNotContain("aspnetcore.components.type", measurements[0].Tags); + Assert.DoesNotContain("code.function.name", measurements[0].Tags); + Assert.DoesNotContain("aspnetcore.components.attribute.name", measurements[0].Tags); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } + + [Fact] + public void FailEventSync_WithNullValues_OmitsTags() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.handle_event.duration"); + var exception = new InvalidOperationException(); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + componentsMetrics.FailEventSync(exception, startTimestamp, null, null, null); + + // Assert + var measurements = eventDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value >= 0); + Assert.DoesNotContain("aspnetcore.components.type", measurements[0].Tags); + Assert.DoesNotContain("code.function.name", measurements[0].Tags); + Assert.DoesNotContain("aspnetcore.components.attribute.name", measurements[0].Tags); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public async Task CaptureParametersDuration_WithNullValues_OmitsTags() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters.duration"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await componentsMetrics.CaptureParametersDuration(Task.CompletedTask, startTimestamp, null); + + // Assert + var measurements = parametersDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value >= 0); + Assert.DoesNotContain("aspnetcore.components.type", measurements[0].Tags); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } + + [Fact] + public void FailParametersSync_WithNullValues_OmitsTags() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters.duration"); + var exception = new InvalidOperationException(); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + componentsMetrics.FailParametersSync(exception, startTimestamp, null); + + // Assert + var measurements = parametersDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value >= 0); + Assert.DoesNotContain("aspnetcore.components.type", measurements[0].Tags); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public async Task CaptureBatchDuration_WithoutException_OmitsErrorTag() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff.duration"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await componentsMetrics.CaptureBatchDuration(Task.CompletedTask, startTimestamp, 25); + + // Assert + var measurements = batchDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value >= 0); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } }