Skip to content

Commit f5aa18a

Browse files
authored
Fix displaying browser link in traces (#9001)
1 parent becd482 commit f5aa18a

File tree

3 files changed

+49
-8
lines changed

3 files changed

+49
-8
lines changed

src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ private void UpdateDetailViewData()
197197
}
198198

199199
Logger.LogInformation("Trace '{TraceId}' has {SpanCount} spans.", _trace.TraceId, _trace.Spans.Count);
200-
_spanWaterfallViewModels = SpanWaterfallViewModel.Create(_trace, new SpanWaterfallViewModel.TraceDetailState(_collapsedSpanIds));
200+
_spanWaterfallViewModels = SpanWaterfallViewModel.Create(_trace, new SpanWaterfallViewModel.TraceDetailState(OutgoingPeerResolvers.ToArray(), _collapsedSpanIds));
201201
_maxDepth = _spanWaterfallViewModels.Max(s => s.Depth);
202202
}
203203

src/Aspire.Dashboard/Model/Otlp/SpanWaterfallViewModel.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private void UpdateHidden(bool isParentCollapsed = false)
107107

108108
private readonly record struct SpanWaterfallViewModelState(SpanWaterfallViewModel? Parent, int Depth, bool Hidden);
109109

110-
public sealed record TraceDetailState(List<string> CollapsedSpanIds);
110+
public sealed record TraceDetailState(IOutgoingPeerResolver[] OutgoingPeerResolvers, List<string> CollapsedSpanIds);
111111

112112
public static string GetTitle(OtlpSpan span, List<OtlpApplication> allApplications)
113113
{
@@ -146,7 +146,7 @@ static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, bool hid
146146
// A span may indicate a call to another service but the service isn't instrumented.
147147
var hasPeerService = OtlpHelpers.GetPeerAddress(span.Attributes) != null;
148148
var isUninstrumentedPeer = hasPeerService && span.Kind is OtlpSpanKind.Client or OtlpSpanKind.Producer && !span.GetChildSpans().Any();
149-
var uninstrumentedPeer = isUninstrumentedPeer ? ResolveUninstrumentedPeerName(span) : null;
149+
var uninstrumentedPeer = isUninstrumentedPeer ? ResolveUninstrumentedPeerName(span, state.OutgoingPeerResolvers) : null;
150150

151151
var viewModel = new SpanWaterfallViewModel
152152
{
@@ -173,14 +173,24 @@ static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, bool hid
173173
}
174174
}
175175

176-
private static string? ResolveUninstrumentedPeerName(OtlpSpan span)
176+
private static string? ResolveUninstrumentedPeerName(OtlpSpan span, IOutgoingPeerResolver[] outgoingPeerResolvers)
177177
{
178178
if (span.UninstrumentedPeer?.ApplicationName is { } peerName)
179179
{
180180
// If the span has a peer name, use it.
181181
return peerName;
182182
}
183183

184+
// Attempt to resolve uninstrumented peer to a friendly name from the span.
185+
// This should only match non-resource returning resolves such as Browser Link.
186+
foreach (var resolver in outgoingPeerResolvers)
187+
{
188+
if (resolver.TryResolvePeer(span.Attributes, out var name, out _))
189+
{
190+
return name;
191+
}
192+
}
193+
184194
// Fallback to the peer address.
185195
return OtlpHelpers.GetPeerAddress(span.Attributes);
186196
}

tests/Aspire.Dashboard.Tests/Model/SpanWaterfallViewModelTests.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Aspire.Dashboard.Model;
45
using Aspire.Dashboard.Model.Otlp;
56
using Aspire.Dashboard.Otlp.Model;
67
using Aspire.Tests.Shared.Telemetry;
@@ -25,7 +26,7 @@ public void Create_HasChildren_ChildrenPopulated()
2526
trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app2, trace, scope, spanId: "1-1", parentSpanId: "1", startDate: new DateTime(2001, 1, 1, 1, 1, 3, DateTimeKind.Utc)));
2627

2728
// Act
28-
var vm = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([]));
29+
var vm = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([], []));
2930

3031
// Assert
3132
Assert.Collection(vm,
@@ -41,6 +42,36 @@ public void Create_HasChildren_ChildrenPopulated()
4142
});
4243
}
4344

45+
[Fact]
46+
public void Create_OutgoingPeers_BrowserLink()
47+
{
48+
// Arrange
49+
var context = new OtlpContext { Logger = NullLogger.Instance, Options = new() };
50+
var app1 = new OtlpApplication("app1", "instance", uninstrumentedPeer: false, context);
51+
var app2 = new OtlpApplication("app2", "instance", uninstrumentedPeer: false, context);
52+
53+
var trace = new OtlpTrace(new byte[] { 1, 2, 3 });
54+
var scope = new OtlpScope(TelemetryTestHelpers.CreateScope(), context);
55+
trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app1, trace, scope, spanId: "1", parentSpanId: null, startDate: new DateTime(2001, 1, 1, 1, 1, 2, DateTimeKind.Utc), kind: OtlpSpanKind.Client, attributes: [KeyValuePair.Create("http.url", "http://localhost:59267/6eed7c2dedc14419901b813e8fe87a86/getScriptTag"), KeyValuePair.Create("server.address", "localhost")]));
56+
trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app2, trace, scope, spanId: "2", parentSpanId: null, startDate: new DateTime(2001, 2, 1, 1, 1, 2, DateTimeKind.Utc), kind: OtlpSpanKind.Client));
57+
58+
// Act
59+
var vm = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([new BrowserLinkOutgoingPeerResolver()], []));
60+
61+
// Assert
62+
Assert.Collection(vm,
63+
e =>
64+
{
65+
Assert.Equal("1", e.Span.SpanId);
66+
Assert.Equal("Browser Link", e.UninstrumentedPeer);
67+
},
68+
e =>
69+
{
70+
Assert.Equal("2", e.Span.SpanId);
71+
Assert.Null(e.UninstrumentedPeer);
72+
});
73+
}
74+
4475
[Theory]
4576
[InlineData("1234", true)] // Matches span ID
4677
[InlineData("app1", true)] // Matches application name
@@ -77,7 +108,7 @@ public void MatchesFilter_VariousCases_ReturnsExpected(string filter, bool expec
77108

78109
var vm = SpanWaterfallViewModel.Create(
79110
trace,
80-
new SpanWaterfallViewModel.TraceDetailState([])).First();
111+
new SpanWaterfallViewModel.TraceDetailState([], [])).First();
81112

82113
// Act
83114
var result = vm.MatchesFilter(filter, a => a.Application.ApplicationName, out _);
@@ -99,7 +130,7 @@ public void MatchesFilter_ParentSpanIncludedWhenChildMatched()
99130
trace.AddSpan(parentSpan);
100131
trace.AddSpan(childSpan);
101132

102-
var vms = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([]));
133+
var vms = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([], []));
103134
var parent = vms[0];
104135
var child = vms[1];
105136

@@ -121,7 +152,7 @@ public void MatchesFilter_ChildSpanIncludedWhenParentMatched()
121152
trace.AddSpan(parentSpan);
122153
trace.AddSpan(childSpan);
123154

124-
var vms = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([]));
155+
var vms = SpanWaterfallViewModel.Create(trace, new SpanWaterfallViewModel.TraceDetailState([], []));
125156
var parent = vms[0];
126157
var child = vms[1];
127158

0 commit comments

Comments
 (0)