Skip to content

Commit fb1942a

Browse files
authored
trace2: add infrastructure and initial events (#1045)
Add initial `TRACE2` functionality, including: 1. Small refactorings to make `TryGetAssemblyVersion` accessible outside of the `DiagnosticCommand` class and shared trace logic accessible from a `TraceUtils` class. 2. Addition of a `Trace2CollectorWriter` class, which handles writing to the Telemetry Tool/OTel collector and a `Trace2StreamWriter` class, which handles writing to stderr/files. 3. Basic `TRACE2` functionality, including the ability to add writers to different format targets, logic to send messages to these writers, and the ability to release these writers before application exit. 4. Support for session IDs. 5. Ability for users to enable normal/event format targets. 6. Writing `Version`, `Start`, and `Exit` events.
2 parents 66b94e4 + 028ad46 commit fb1942a

File tree

33 files changed

+964
-99
lines changed

33 files changed

+964
-99
lines changed

src/shared/Atlassian.Bitbucket.UI.Avalonia/Program.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.CommandLine;
2+
using System.Diagnostics;
33
using System.Threading;
44
using Atlassian.Bitbucket.UI.Commands;
55
using Atlassian.Bitbucket.UI.Controls;
@@ -45,9 +45,7 @@ private static void AppMain(object o)
4545
{
4646
string[] args = (string[]) o;
4747

48-
string appPath = ApplicationBase.GetEntryApplicationPath();
49-
string installDir = ApplicationBase.GetInstallationDirectory();
50-
using (var context = new CommandContext(appPath, installDir))
48+
using (var context = new CommandContext(args))
5149
using (var app = new HelperApplication(context))
5250
{
5351
app.RegisterCommand(new CredentialsCommandImpl(context));

src/shared/Core.Tests/EnvironmentTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,37 @@ public void MacOSEnvironment_TryLocateExecutable_Paths_Are_Ignored()
150150
Assert.True(actualResult);
151151
Assert.Equal(expectedPath, actualPath);
152152
}
153+
154+
[PlatformFact(Platforms.Posix)]
155+
public void PosixEnvironment_SetEnvironmentVariable_Sets_Expected_Value()
156+
{
157+
var variable = "FOO_BAR";
158+
var value = "baz";
159+
160+
var fs = new TestFileSystem();
161+
var envars = new Dictionary<string, string>();
162+
var env = new PosixEnvironment(fs, envars);
163+
164+
env.SetEnvironmentVariable(variable, value);
165+
166+
Assert.Contains(env.Variables, item
167+
=> item.Key.Equals(variable) && item.Value.Equals(value));
168+
}
169+
170+
[PlatformFact(Platforms.Windows)]
171+
public void WindowsEnvironment_SetEnvironmentVariable_Sets_Expected_Value()
172+
{
173+
var variable = "FOO_BAR";
174+
var value = "baz";
175+
176+
var fs = new TestFileSystem();
177+
var envars = new Dictionary<string, string>();
178+
var env = new WindowsEnvironment(fs, envars);
179+
180+
env.SetEnvironmentVariable(variable, value);
181+
182+
Assert.Contains(env.Variables, item
183+
=> item.Key.Equals(variable) && item.Value.Equals(value));
184+
}
153185
}
154186
}

src/shared/Core.Tests/StringExtensionsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ public void StringExtensions_TrimUntilLastIndexOf_Character_Null_ThrowsArgumentN
245245
[InlineData("foo://", "://", "")]
246246
[InlineData("foo://bar", "://", "bar")]
247247
[InlineData("foo://bar/", "://", "bar/")]
248+
[InlineData("foo:/bar/baz", ":", "/bar/baz")]
248249
public void StringExtensions_TrimUntilLastIndexOf_String(string input, string trim, string expected)
249250
{
250251
string actual = StringExtensions.TrimUntilLastIndexOf(input, trim);

src/shared/Core.Tests/Trace2Tests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
using GitCredentialManager.Tests.Objects;
4+
using Xunit;
5+
6+
namespace GitCredentialManager.Tests;
7+
8+
public class Trace2Tests
9+
{
10+
[PlatformTheory(Platforms.Posix)]
11+
[InlineData("af_unix:foo", "foo")]
12+
[InlineData("af_unix:stream:foo-bar", "foo-bar")]
13+
[InlineData("af_unix:dgram:foo-bar-baz", "foo-bar-baz")]
14+
public void TryParseEventTarget_Posix_Returns_Expected_Value(string input, string expected)
15+
{
16+
var environment = new TestEnvironment();
17+
var settings = new TestSettings();
18+
19+
var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
20+
var isSuccessful = trace2.TryGetPipeName(input, out var actual);
21+
22+
Assert.True(isSuccessful);
23+
Assert.Matches(actual, expected);
24+
}
25+
26+
[PlatformTheory(Platforms.Windows)]
27+
[InlineData("\\\\.\\pipe\\git-foo", "git-foo")]
28+
[InlineData("\\\\.\\pipe\\git-foo-bar", "git-foo-bar")]
29+
[InlineData("\\\\.\\pipe\\foo\\git-bar", "git-bar")]
30+
public void TryParseEventTarget_Windows_Returns_Expected_Value(string input, string expected)
31+
{
32+
var environment = new TestEnvironment();
33+
var settings = new TestSettings();
34+
35+
var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
36+
var isSuccessful = trace2.TryGetPipeName(input, out var actual);
37+
38+
Assert.True(isSuccessful);
39+
Assert.Matches(actual, expected);
40+
}
41+
42+
[Theory]
43+
[InlineData("20190408T191610.507018Z-H9b68c35f-P000059a8")]
44+
[InlineData("")]
45+
public void SetSid_Envar_Returns_Expected_Value(string parentSid)
46+
{
47+
Regex rx = new Regex(@$"{parentSid}\/[\d\w-]*");
48+
49+
var environment = new TestEnvironment();
50+
environment.Variables.Add("GIT_TRACE2_PARENT_SID", parentSid);
51+
52+
var settings = new TestSettings();
53+
var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
54+
var sid = trace2.SetSid();
55+
56+
Assert.Matches(rx, sid);
57+
}
58+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.IO;
3+
using System.Text;
4+
using Xunit;
5+
6+
namespace GitCredentialManager.Tests;
7+
8+
public class TraceUtilsTests
9+
{
10+
[Theory]
11+
[InlineData("/foo/bar/baz/boo", 10, "...baz/boo")]
12+
[InlineData("thisfileshouldbetruncated", 12, "...truncated")]
13+
public void FormatSource_ReturnsExpectedSourceValues(string path, int sourceColumnMaxWidth, string expectedSource)
14+
{
15+
string actualSource = TraceUtils.FormatSource(path, sourceColumnMaxWidth);
16+
Assert.Equal(actualSource, expectedSource);
17+
}
18+
}

src/shared/Core/ApplicationBase.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.IO;
45
using System.Reflection;
6+
using System.IO.Pipes;
57
using System.Text;
68
using System.Threading;
79
using System.Threading.Tasks;
@@ -75,25 +77,16 @@ public Task<int> RunAsync(string[] args)
7577
Context.Trace.WriteLine("Tracing of secrets is enabled. Trace output may contain sensitive information.");
7678
}
7779

80+
// Enable TRACE2 tracing
81+
Context.Trace2.Start(Context.Streams.Error, Context.FileSystem, Context.ApplicationPath);
82+
7883
return RunInternalAsync(args);
7984
}
8085

8186
protected abstract Task<int> RunInternalAsync(string[] args);
8287

8388
#region Helpers
8489

85-
public static string GetEntryApplicationPath()
86-
{
87-
return PlatformUtils.GetNativeEntryPath() ??
88-
Process.GetCurrentProcess().MainModule?.FileName ??
89-
Environment.GetCommandLineArgs()[0];
90-
}
91-
92-
public static string GetInstallationDirectory()
93-
{
94-
return AppContext.BaseDirectory;
95-
}
96-
9790
/// <summary>
9891
/// Wait until a debugger has attached to the currently executing process.
9992
/// </summary>

src/shared/Core/AssemblyUtils.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Reflection;
2+
3+
namespace GitCredentialManager;
4+
5+
public static class AssemblyUtils
6+
{
7+
public static bool TryGetAssemblyVersion(out string version)
8+
{
9+
try
10+
{
11+
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
12+
var assemblyVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
13+
version = assemblyVersionAttribute is null
14+
? assembly.GetName().Version.ToString()
15+
: assemblyVersionAttribute.InformationalVersion;
16+
return true;
17+
}
18+
catch
19+
{
20+
version = null;
21+
return false;
22+
}
23+
}
24+
}

src/shared/Core/CommandContext.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
24
using System.IO;
35
using GitCredentialManager.Interop.Linux;
46
using GitCredentialManager.Interop.MacOS;
@@ -15,7 +17,7 @@ public interface ICommandContext : IDisposable
1517
/// <summary>
1618
/// Absolute path the application entry executable.
1719
/// </summary>
18-
string ApplicationPath { get; }
20+
string ApplicationPath { get; set; }
1921

2022
/// <summary>
2123
/// Absolute path to the Git Credential Manager installation directory.
@@ -47,6 +49,11 @@ public interface ICommandContext : IDisposable
4749
/// </summary>
4850
ITrace Trace { get; }
4951

52+
/// <summary>
53+
/// Application TRACE2 tracing system.
54+
/// </summary>
55+
ITrace2 Trace2 { get; }
56+
5057
/// <summary>
5158
/// File system abstraction (exists mainly for testing).
5259
/// </summary>
@@ -78,12 +85,11 @@ public interface ICommandContext : IDisposable
7885
/// </summary>
7986
public class CommandContext : DisposableObject, ICommandContext
8087
{
81-
public CommandContext(string appPath, string installDir)
88+
public CommandContext(string[] argv)
8289
{
83-
EnsureArgument.NotNullOrWhiteSpace(appPath, nameof (appPath));
84-
85-
ApplicationPath = appPath;
86-
InstallationDirectory = installDir;
90+
var applicationStartTime = DateTimeOffset.UtcNow;
91+
ApplicationPath = GetEntryApplicationPath();
92+
InstallationDirectory = GetInstallationDirectory();
8793

8894
Streams = new StandardStreams();
8995
Trace = new Trace();
@@ -139,6 +145,7 @@ public CommandContext(string appPath, string installDir)
139145
throw new PlatformNotSupportedException();
140146
}
141147

148+
Trace2 = new Trace2(Environment, Settings.GetTrace2Settings(), argv, applicationStartTime);
142149
HttpClientFactory = new HttpClientFactory(FileSystem, Trace, Settings, Streams);
143150
CredentialStore = new CredentialStore(this);
144151
}
@@ -177,7 +184,7 @@ private static string GetGitPath(IEnvironment environment, IFileSystem fileSyste
177184

178185
#region ICommandContext
179186

180-
public string ApplicationPath { get; }
187+
public string ApplicationPath { get; set; }
181188

182189
public string InstallationDirectory { get; }
183190

@@ -191,6 +198,8 @@ private static string GetGitPath(IEnvironment environment, IFileSystem fileSyste
191198

192199
public ITrace Trace { get; }
193200

201+
public ITrace2 Trace2 { get; }
202+
194203
public IFileSystem FileSystem { get; }
195204

196205
public ICredentialStore CredentialStore { get; }
@@ -214,5 +223,17 @@ protected override void ReleaseManagedResources()
214223
}
215224

216225
#endregion
226+
227+
public static string GetEntryApplicationPath()
228+
{
229+
return PlatformUtils.GetNativeEntryPath() ??
230+
Process.GetCurrentProcess().MainModule?.FileName ??
231+
System.Environment.GetCommandLineArgs()[0];
232+
}
233+
234+
public static string GetInstallationDirectory()
235+
{
236+
return AppContext.BaseDirectory;
237+
}
217238
}
218239
}

src/shared/Core/Commands/DiagnoseCommand.cs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private async Task<int> ExecuteAsync(string output)
8686
fullLog.WriteLine($"AppPath: {_context.ApplicationPath}");
8787
fullLog.WriteLine($"InstallDir: {_context.InstallationDirectory}");
8888
fullLog.WriteLine(
89-
TryGetAssemblyVersion(out string version)
89+
AssemblyUtils.TryGetAssemblyVersion(out string version)
9090
? $"Version: {version}"
9191
: "Version: [!] Failed to get version information [!]"
9292
);
@@ -198,24 +198,6 @@ private async Task<int> ExecuteAsync(string output)
198198
return numFailed;
199199
}
200200

201-
private bool TryGetAssemblyVersion(out string version)
202-
{
203-
try
204-
{
205-
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
206-
var assemblyVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
207-
version = assemblyVersionAttribute is null
208-
? assembly.GetName().Version.ToString()
209-
: assemblyVersionAttribute.InformationalVersion;
210-
return true;
211-
}
212-
catch
213-
{
214-
version = null;
215-
return false;
216-
}
217-
}
218-
219201
private static class ConsoleEx
220202
{
221203
public static void WriteLineIndent(string str)

src/shared/Core/Constants.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public static class EnvironmentVariables
5454
public const string GcmAuthority = "GCM_AUTHORITY";
5555
public const string GitTerminalPrompts = "GIT_TERMINAL_PROMPT";
5656
public const string GcmAllowWia = "GCM_ALLOW_WINDOWSAUTH";
57+
public const string GitTrace2Event = "GIT_TRACE2_EVENT";
58+
public const string GitTrace2Normal = "GIT_TRACE2";
5759

5860
/*
5961
* Unlike other environment variables, these proxy variables are normally lowercase only.
@@ -164,6 +166,13 @@ public static class Remote
164166
public const string FetchUrl = "url";
165167
public const string PushUrl = "pushUrl";
166168
}
169+
170+
public static class Trace2
171+
{
172+
public const string SectionName = "trace2";
173+
public const string EventTarget = "eventtarget";
174+
public const string NormalTarget = "normaltarget";
175+
}
167176
}
168177

169178
public static class WindowsRegistry

0 commit comments

Comments
 (0)