Skip to content

Commit 6692aa8

Browse files
committed
get context flowing across dotnet CLI calls
1 parent 6ea5a1f commit 6692aa8

File tree

3 files changed

+111
-10
lines changed

3 files changed

+111
-10
lines changed

src/Cli/dotnet/Activities.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ namespace Microsoft.DotNet.Cli;
77
public static class Activities
88
{
99
public static ActivitySource s_source = new("dotnet-cli", Product.Version);
10+
11+
public const string DOTNET_CLI_TRACEPARENT = nameof(DOTNET_CLI_TRACEPARENT);
12+
public const string DOTNET_CLI_TRACESTATE = nameof(DOTNET_CLI_TRACESTATE);
1013
}
Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,54 @@
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-
#nullable disable
5-
4+
using System.Diagnostics;
65
using Microsoft.DotNet.Cli.Utils;
76
using Microsoft.DotNet.Cli.Utils.Extensions;
7+
using OpenTelemetry;
8+
using OpenTelemetry.Context.Propagation;
89

910
namespace Microsoft.DotNet.Cli.CommandFactory.CommandResolution;
1011

1112
public class MuxerCommandResolver : ICommandResolver
1213
{
13-
public CommandSpec Resolve(CommandResolverArguments commandResolverArguments)
14+
public CommandSpec? Resolve(CommandResolverArguments commandResolverArguments)
1415
{
1516
if (commandResolverArguments.CommandName == Muxer.MuxerName)
1617
{
1718
var muxer = new Muxer();
1819
var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(
1920
commandResolverArguments.CommandArguments.OrEmptyIfNull());
20-
return new CommandSpec(muxer.MuxerPath, escapedArgs);
21+
var env = MakeActivityContextEnvironment();
22+
return new CommandSpec(muxer.MuxerPath, escapedArgs, env);
2123
}
2224
return null;
2325
}
26+
27+
private Dictionary<string, string>? MakeActivityContextEnvironment()
28+
{
29+
var currentActivity = Activity.Current;
30+
var currentBaggage = Baggage.Current;
31+
if (currentActivity == null)
32+
{
33+
return null;
34+
}
35+
var contextToInject = currentActivity.Context;
36+
var propagationContext = new PropagationContext(contextToInject, currentBaggage);
37+
var envDict = new Dictionary<string, string>();
38+
Propagators.DefaultTextMapPropagator.Inject(propagationContext, envDict, WriteTraceStateIntoEnv);
39+
return envDict;
40+
}
41+
42+
private void WriteTraceStateIntoEnv(Dictionary<string, string> dictionary, string key, string value)
43+
{
44+
switch (key)
45+
{
46+
case "traceparent":
47+
dictionary[Activities.DOTNET_CLI_TRACEPARENT] = value;
48+
break;
49+
case "tracestate":
50+
dictionary[Activities.DOTNET_CLI_TRACESTATE] = value;
51+
break;
52+
}
53+
}
2454
}

src/Cli/dotnet/Program.cs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Extensions.EnvironmentAbstractions;
2020
using NuGet.Frameworks;
2121
using OpenTelemetry;
22+
using OpenTelemetry.Context.Propagation;
2223
using OpenTelemetry.Metrics;
2324
using OpenTelemetry.Resources;
2425
using OpenTelemetry.Trace;
@@ -56,12 +57,16 @@ public class Program
5657
.AddOtlpExporter()
5758
.Build();
5859

60+
private static ActivityContext s_parentActivityContext = default;
61+
private static ActivityKind s_activityKind = ActivityKind.Internal;
62+
5963

6064
public static int Main(string[] args)
6165
{
6266
// capture the time to we can compute muxer/host startup overhead
6367
DateTime mainTimeStamp = DateTime.Now;
64-
using var _mainActivity = Activities.s_source.StartActivity("main");
68+
(s_parentActivityContext, s_activityKind) = DeriveParentActivityContextFromEnv();
69+
using var _mainActivity = Activities.s_source.StartActivity("main", s_activityKind, s_parentActivityContext);
6570
using AutomaticEncodingRestorer _encodingRestorer = new();
6671

6772
// Setting output encoding is not available on those platforms
@@ -119,9 +124,44 @@ public static int Main(string[] args)
119124
}
120125
}
121126

127+
/// <summary>
128+
/// uses the OpenTelemetrySDK's Propagation API to derive the parent activity context and kind
129+
/// from the DOTNET_CLI_TRACEPARENT and DOTNET_CLI_TRACESTATE environment variables.
130+
/// </summary>
131+
/// <returns></returns>
132+
private static (ActivityContext parentActivityContext, ActivityKind kind) DeriveParentActivityContextFromEnv()
133+
{
134+
var traceParent = Env.GetEnvironmentVariable(Activities.DOTNET_CLI_TRACEPARENT);
135+
var traceState = Env.GetEnvironmentVariable(Activities.DOTNET_CLI_TRACESTATE);
136+
static IEnumerable<string> GetValueFromCarrier(Dictionary<string, IEnumerable<string>> carrier, string key)
137+
{
138+
return carrier.TryGetValue(key, out var value) ? value : Enumerable.Empty<string>();
139+
}
140+
141+
if (string.IsNullOrEmpty(traceParent))
142+
{
143+
return (default, ActivityKind.Internal);
144+
}
145+
var carriermap = new Dictionary<string, IEnumerable<string>>
146+
{
147+
{ "traceparent", [traceParent] },
148+
{ "tracestate", [traceState] }
149+
};
150+
151+
// Use the OpenTelemetry Propagator to extract the parent activity context and kind. For some reason this isn't set by the OTel SDK like docs say it should be.
152+
Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator([
153+
new TraceContextPropagator(),
154+
new BaggagePropagator()
155+
]));
156+
var parentActivityContext = Propagators.DefaultTextMapPropagator.Extract(default, carriermap, GetValueFromCarrier);
157+
var kind = parentActivityContext.ActivityContext.IsRemote ? ActivityKind.Server : ActivityKind.Internal;
158+
159+
return (parentActivityContext.ActivityContext, kind);
160+
}
161+
122162
private static void TrackHostStartup(DateTime mainTimeStamp)
123163
{
124-
var hostStartupActivity = Activities.s_source.CreateActivity("host-startup", ActivityKind.Server);
164+
var hostStartupActivity = Activities.s_source.CreateActivity("host-startup", s_activityKind, s_parentActivityContext);
125165
hostStartupActivity?.SetStartTime(Process.GetCurrentProcess().StartTime);
126166
hostStartupActivity?.SetEndTime(mainTimeStamp);
127167
hostStartupActivity?.SetStatus(ActivityStatusCode.Ok);
@@ -140,20 +180,48 @@ private static void SetupMSBuildEnvironmentInvariants()
140180
}
141181
}
142182

143-
internal static int ProcessArgs(string[] args)
183+
private static string GetCommandName(ParseResult r)
184+
{
185+
// walk the parent command tree to find the top-level command name and get the full command name for this parseresult
186+
List<string> parentNames = [r.CommandResult.Command.Name];
187+
var current = r.CommandResult.Parent;
188+
while (current is CommandResult parentCommandResult)
189+
{
190+
parentNames.Add(parentCommandResult.Command.Name);
191+
current = parentCommandResult.Parent;
192+
}
193+
parentNames.Reverse();
194+
return string.Join(' ', parentNames);
195+
}
196+
private static void SetDisplayName(Activity activity, ParseResult parseResult)
197+
{
198+
if (activity == null)
199+
{
200+
return;
201+
}
202+
var name = GetCommandName(parseResult);
203+
// Set the display name to the full command name
204+
activity.DisplayName = name;
205+
206+
// Set the command name as an attribute for better filtering in telemetry
207+
activity.SetTag("command.name", name);
208+
}
209+
210+
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null)
144211
{
145212
ParseResult parseResult;
146-
using (var _parseActivity = Activities.s_source.StartActivity("parse"))
213+
using (var _parseActivity = Activities.s_source.StartActivity("parse", s_activityKind, s_parentActivityContext))
147214
{
148215
parseResult = Parser.Instance.Parse(args);
149216

217+
SetDisplayName(_parseActivity, parseResult);
150218
// Avoid create temp directory with root permission and later prevent access in non sudo
151219
// This method need to be run very early before temp folder get created
152220
// https://github.com/dotnet/sdk/issues/20195
153221
SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult);
154222
}
155223

156-
using (var _firstTimeUseActivity = Activities.s_source.StartActivity("first-time-use"))
224+
using (var _firstTimeUseActivity = Activities.s_source.StartActivity("first-time-use", s_activityKind, s_parentActivityContext))
157225
{
158226
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel();
159227

@@ -309,7 +377,7 @@ static void InvokeBuiltInCommand(ParseResult parseResult, out int exitCode)
309377
}
310378
}
311379
}
312-
380+
313381
private static int AdjustExitCode(ParseResult parseResult, int exitCode)
314382
{
315383
if (parseResult.Errors.Count > 0)

0 commit comments

Comments
 (0)