From f26d09088103159652da321296697e7e87c2347f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Mar 2026 15:32:20 +0800 Subject: [PATCH 1/4] Simplify TelemetryApiService.GetTrace and support short trace IDs - Use TelemetryRepository.GetTrace instead of fetching all traces - Add early return in GetTraceUnsynchronized for IDs shorter than ShortenedIdLength - Add parameterized unit test for full, short, and nonexistent trace IDs --- .../Api/TelemetryApiService.cs | 11 +---- .../Otlp/Storage/TelemetryRepository.cs | 5 +++ .../TelemetryApiServiceTests.cs | 43 +++++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/Aspire.Dashboard/Api/TelemetryApiService.cs b/src/Aspire.Dashboard/Api/TelemetryApiService.cs index 112c2502a79..80617479ca7 100644 --- a/src/Aspire.Dashboard/Api/TelemetryApiService.cs +++ b/src/Aspire.Dashboard/Api/TelemetryApiService.cs @@ -167,16 +167,7 @@ internal sealed class TelemetryApiService( /// public TelemetryApiResponse? GetTrace(string traceId) { - var result = telemetryRepository.GetTraces(new GetTracesRequest - { - ResourceKey = null, - StartIndex = 0, - Count = MaxQueryCount, - Filters = [], - FilterText = string.Empty - }); - - var trace = result.PagedResult.Items.FirstOrDefault(t => OtlpHelpers.MatchTelemetryId(t.TraceId, traceId)); + var trace = telemetryRepository.GetTrace(traceId); if (trace is null) { return null; diff --git a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs index 0d46579ccd3..45e22503b17 100644 --- a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs +++ b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs @@ -995,6 +995,11 @@ public bool HasUpdatedTrace(OtlpTrace trace) { Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetTraceUnsynchronized)}."); + if (traceId.Length < OtlpHelpers.ShortenedIdLength) + { + return null; + } + foreach (var trace in _traces) { if (OtlpHelpers.MatchTelemetryId(traceId, trace.TraceId)) diff --git a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs index 08be66566a8..e00a307391c 100644 --- a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs +++ b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs @@ -408,6 +408,49 @@ public async Task FollowLogsAsync_WithInvalidResourceName_ReturnsNoLogs() Assert.Empty(receivedItems); } + [Theory] + [InlineData("747261636531", true)] // full hex trace ID for "trace1" + [InlineData("7472616", true)] // shortened (7 char) prefix + [InlineData("747261", false)] // too short + [InlineData("nonexistent", false)] + public void GetTrace_VariousTraceIds_ReturnsExpectedResult(string lookupId, bool expectFound) + { + var repository = CreateRepository(); + + repository.AddTraces(new AddContext(), new RepeatedField + { + new ResourceSpans + { + Resource = CreateResource(name: "service1", instanceId: "inst1"), + ScopeSpans = + { + new ScopeSpans + { + Scope = CreateScope(), + Spans = + { + CreateSpan(traceId: "trace1", spanId: "span1", startTime: s_testTime, endTime: s_testTime.AddMinutes(1)) + } + } + } + } + }); + + var service = CreateService(repository); + + var result = service.GetTrace(lookupId); + + if (expectFound) + { + Assert.NotNull(result); + Assert.Equal(1, result.ReturnedCount); + } + else + { + Assert.Null(result); + } + } + /// /// Creates a TelemetryApiService instance for testing with optional custom dependencies. /// From dd4d75930fa410a06b99cfc4b4bc42cf0f14d73f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Mar 2026 15:38:49 +0800 Subject: [PATCH 2/4] Use hex constant in GetTrace test for explicit trace ID relationship --- tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs index e00a307391c..92f52b3c23d 100644 --- a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs +++ b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text; using Aspire.Dashboard.Api; using Aspire.Dashboard.Model; using Aspire.Dashboard.Otlp.Model; @@ -416,6 +417,7 @@ public async Task FollowLogsAsync_WithInvalidResourceName_ReturnsNoLogs() public void GetTrace_VariousTraceIds_ReturnsExpectedResult(string lookupId, bool expectFound) { var repository = CreateRepository(); + var traceId = Encoding.UTF8.GetString(Convert.FromHexString("747261636531")); repository.AddTraces(new AddContext(), new RepeatedField { @@ -429,7 +431,7 @@ public void GetTrace_VariousTraceIds_ReturnsExpectedResult(string lookupId, bool Scope = CreateScope(), Spans = { - CreateSpan(traceId: "trace1", spanId: "span1", startTime: s_testTime, endTime: s_testTime.AddMinutes(1)) + CreateSpan(traceId: traceId, spanId: "span1", startTime: s_testTime, endTime: s_testTime.AddMinutes(1)) } } } From 70109f2543788a98dceb121ff56e1be0c7107971 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Mar 2026 15:42:24 +0800 Subject: [PATCH 3/4] Remove early return for short trace IDs in GetTraceUnsynchronized MatchTelemetryId already handles short IDs correctly with exact equality fallback, so the guard was preventing valid exact-match lookups. --- src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs index 45e22503b17..0d46579ccd3 100644 --- a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs +++ b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs @@ -995,11 +995,6 @@ public bool HasUpdatedTrace(OtlpTrace trace) { Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetTraceUnsynchronized)}."); - if (traceId.Length < OtlpHelpers.ShortenedIdLength) - { - return null; - } - foreach (var trace in _traces) { if (OtlpHelpers.MatchTelemetryId(traceId, trace.TraceId)) From c6b1542b0fe6d8631942c07ebd85dbdbf57cdb31 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Mar 2026 15:47:40 +0800 Subject: [PATCH 4/4] Clean up --- tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs index 92f52b3c23d..8bd11576896 100644 --- a/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs +++ b/tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs @@ -410,7 +410,7 @@ public async Task FollowLogsAsync_WithInvalidResourceName_ReturnsNoLogs() } [Theory] - [InlineData("747261636531", true)] // full hex trace ID for "trace1" + [InlineData("747261636531", true)] // full hex trace ID [InlineData("7472616", true)] // shortened (7 char) prefix [InlineData("747261", false)] // too short [InlineData("nonexistent", false)]