Skip to content

Commit

Permalink
trace2: add infrastructure and initial events (#1045)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ldennington committed Feb 10, 2023
2 parents 66b94e4 + 028ad46 commit fb1942a
Show file tree
Hide file tree
Showing 33 changed files with 964 additions and 99 deletions.
6 changes: 2 additions & 4 deletions src/shared/Atlassian.Bitbucket.UI.Avalonia/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.CommandLine;
using System.Diagnostics;
using System.Threading;
using Atlassian.Bitbucket.UI.Commands;
using Atlassian.Bitbucket.UI.Controls;
Expand Down Expand Up @@ -45,9 +45,7 @@ private static void AppMain(object o)
{
string[] args = (string[]) o;

string appPath = ApplicationBase.GetEntryApplicationPath();
string installDir = ApplicationBase.GetInstallationDirectory();
using (var context = new CommandContext(appPath, installDir))
using (var context = new CommandContext(args))
using (var app = new HelperApplication(context))
{
app.RegisterCommand(new CredentialsCommandImpl(context));
Expand Down
32 changes: 32 additions & 0 deletions src/shared/Core.Tests/EnvironmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,37 @@ public void MacOSEnvironment_TryLocateExecutable_Paths_Are_Ignored()
Assert.True(actualResult);
Assert.Equal(expectedPath, actualPath);
}

[PlatformFact(Platforms.Posix)]
public void PosixEnvironment_SetEnvironmentVariable_Sets_Expected_Value()
{
var variable = "FOO_BAR";
var value = "baz";

var fs = new TestFileSystem();
var envars = new Dictionary<string, string>();
var env = new PosixEnvironment(fs, envars);

env.SetEnvironmentVariable(variable, value);

Assert.Contains(env.Variables, item
=> item.Key.Equals(variable) && item.Value.Equals(value));
}

[PlatformFact(Platforms.Windows)]
public void WindowsEnvironment_SetEnvironmentVariable_Sets_Expected_Value()
{
var variable = "FOO_BAR";
var value = "baz";

var fs = new TestFileSystem();
var envars = new Dictionary<string, string>();
var env = new WindowsEnvironment(fs, envars);

env.SetEnvironmentVariable(variable, value);

Assert.Contains(env.Variables, item
=> item.Key.Equals(variable) && item.Value.Equals(value));
}
}
}
1 change: 1 addition & 0 deletions src/shared/Core.Tests/StringExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ public void StringExtensions_TrimUntilLastIndexOf_Character_Null_ThrowsArgumentN
[InlineData("foo://", "://", "")]
[InlineData("foo://bar", "://", "bar")]
[InlineData("foo://bar/", "://", "bar/")]
[InlineData("foo:/bar/baz", ":", "/bar/baz")]
public void StringExtensions_TrimUntilLastIndexOf_String(string input, string trim, string expected)
{
string actual = StringExtensions.TrimUntilLastIndexOf(input, trim);
Expand Down
58 changes: 58 additions & 0 deletions src/shared/Core.Tests/Trace2Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Text.RegularExpressions;
using GitCredentialManager.Tests.Objects;
using Xunit;

namespace GitCredentialManager.Tests;

public class Trace2Tests
{
[PlatformTheory(Platforms.Posix)]
[InlineData("af_unix:foo", "foo")]
[InlineData("af_unix:stream:foo-bar", "foo-bar")]
[InlineData("af_unix:dgram:foo-bar-baz", "foo-bar-baz")]
public void TryParseEventTarget_Posix_Returns_Expected_Value(string input, string expected)
{
var environment = new TestEnvironment();
var settings = new TestSettings();

var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
var isSuccessful = trace2.TryGetPipeName(input, out var actual);

Assert.True(isSuccessful);
Assert.Matches(actual, expected);
}

[PlatformTheory(Platforms.Windows)]
[InlineData("\\\\.\\pipe\\git-foo", "git-foo")]
[InlineData("\\\\.\\pipe\\git-foo-bar", "git-foo-bar")]
[InlineData("\\\\.\\pipe\\foo\\git-bar", "git-bar")]
public void TryParseEventTarget_Windows_Returns_Expected_Value(string input, string expected)
{
var environment = new TestEnvironment();
var settings = new TestSettings();

var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
var isSuccessful = trace2.TryGetPipeName(input, out var actual);

Assert.True(isSuccessful);
Assert.Matches(actual, expected);
}

[Theory]
[InlineData("20190408T191610.507018Z-H9b68c35f-P000059a8")]
[InlineData("")]
public void SetSid_Envar_Returns_Expected_Value(string parentSid)
{
Regex rx = new Regex(@$"{parentSid}\/[\d\w-]*");

var environment = new TestEnvironment();
environment.Variables.Add("GIT_TRACE2_PARENT_SID", parentSid);

var settings = new TestSettings();
var trace2 = new Trace2(environment, settings.GetTrace2Settings(), new []{""}, DateTimeOffset.UtcNow);
var sid = trace2.SetSid();

Assert.Matches(rx, sid);
}
}
18 changes: 18 additions & 0 deletions src/shared/Core.Tests/TraceUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.IO;
using System.Text;
using Xunit;

namespace GitCredentialManager.Tests;

public class TraceUtilsTests
{
[Theory]
[InlineData("/foo/bar/baz/boo", 10, "...baz/boo")]
[InlineData("thisfileshouldbetruncated", 12, "...truncated")]
public void FormatSource_ReturnsExpectedSourceValues(string path, int sourceColumnMaxWidth, string expectedSource)
{
string actualSource = TraceUtils.FormatSource(path, sourceColumnMaxWidth);
Assert.Equal(actualSource, expectedSource);
}
}
17 changes: 5 additions & 12 deletions src/shared/Core/ApplicationBase.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -75,25 +77,16 @@ public Task<int> RunAsync(string[] args)
Context.Trace.WriteLine("Tracing of secrets is enabled. Trace output may contain sensitive information.");
}

// Enable TRACE2 tracing
Context.Trace2.Start(Context.Streams.Error, Context.FileSystem, Context.ApplicationPath);

return RunInternalAsync(args);
}

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

#region Helpers

public static string GetEntryApplicationPath()
{
return PlatformUtils.GetNativeEntryPath() ??
Process.GetCurrentProcess().MainModule?.FileName ??
Environment.GetCommandLineArgs()[0];
}

public static string GetInstallationDirectory()
{
return AppContext.BaseDirectory;
}

/// <summary>
/// Wait until a debugger has attached to the currently executing process.
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions src/shared/Core/AssemblyUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Reflection;

namespace GitCredentialManager;

public static class AssemblyUtils
{
public static bool TryGetAssemblyVersion(out string version)
{
try
{
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
var assemblyVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
version = assemblyVersionAttribute is null
? assembly.GetName().Version.ToString()
: assemblyVersionAttribute.InformationalVersion;
return true;
}
catch
{
version = null;
return false;
}
}
}
35 changes: 28 additions & 7 deletions src/shared/Core/CommandContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using GitCredentialManager.Interop.Linux;
using GitCredentialManager.Interop.MacOS;
Expand All @@ -15,7 +17,7 @@ public interface ICommandContext : IDisposable
/// <summary>
/// Absolute path the application entry executable.
/// </summary>
string ApplicationPath { get; }
string ApplicationPath { get; set; }

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

/// <summary>
/// Application TRACE2 tracing system.
/// </summary>
ITrace2 Trace2 { get; }

/// <summary>
/// File system abstraction (exists mainly for testing).
/// </summary>
Expand Down Expand Up @@ -78,12 +85,11 @@ public interface ICommandContext : IDisposable
/// </summary>
public class CommandContext : DisposableObject, ICommandContext
{
public CommandContext(string appPath, string installDir)
public CommandContext(string[] argv)
{
EnsureArgument.NotNullOrWhiteSpace(appPath, nameof (appPath));

ApplicationPath = appPath;
InstallationDirectory = installDir;
var applicationStartTime = DateTimeOffset.UtcNow;
ApplicationPath = GetEntryApplicationPath();
InstallationDirectory = GetInstallationDirectory();

Streams = new StandardStreams();
Trace = new Trace();
Expand Down Expand Up @@ -139,6 +145,7 @@ public CommandContext(string appPath, string installDir)
throw new PlatformNotSupportedException();
}

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

#region ICommandContext

public string ApplicationPath { get; }
public string ApplicationPath { get; set; }

public string InstallationDirectory { get; }

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

public ITrace Trace { get; }

public ITrace2 Trace2 { get; }

public IFileSystem FileSystem { get; }

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

#endregion

public static string GetEntryApplicationPath()
{
return PlatformUtils.GetNativeEntryPath() ??
Process.GetCurrentProcess().MainModule?.FileName ??
System.Environment.GetCommandLineArgs()[0];
}

public static string GetInstallationDirectory()
{
return AppContext.BaseDirectory;
}
}
}
20 changes: 1 addition & 19 deletions src/shared/Core/Commands/DiagnoseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private async Task<int> ExecuteAsync(string output)
fullLog.WriteLine($"AppPath: {_context.ApplicationPath}");
fullLog.WriteLine($"InstallDir: {_context.InstallationDirectory}");
fullLog.WriteLine(
TryGetAssemblyVersion(out string version)
AssemblyUtils.TryGetAssemblyVersion(out string version)
? $"Version: {version}"
: "Version: [!] Failed to get version information [!]"
);
Expand Down Expand Up @@ -198,24 +198,6 @@ private async Task<int> ExecuteAsync(string output)
return numFailed;
}

private bool TryGetAssemblyVersion(out string version)
{
try
{
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
var assemblyVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
version = assemblyVersionAttribute is null
? assembly.GetName().Version.ToString()
: assemblyVersionAttribute.InformationalVersion;
return true;
}
catch
{
version = null;
return false;
}
}

private static class ConsoleEx
{
public static void WriteLineIndent(string str)
Expand Down
9 changes: 9 additions & 0 deletions src/shared/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public static class EnvironmentVariables
public const string GcmAuthority = "GCM_AUTHORITY";
public const string GitTerminalPrompts = "GIT_TERMINAL_PROMPT";
public const string GcmAllowWia = "GCM_ALLOW_WINDOWSAUTH";
public const string GitTrace2Event = "GIT_TRACE2_EVENT";
public const string GitTrace2Normal = "GIT_TRACE2";

/*
* Unlike other environment variables, these proxy variables are normally lowercase only.
Expand Down Expand Up @@ -164,6 +166,13 @@ public static class Remote
public const string FetchUrl = "url";
public const string PushUrl = "pushUrl";
}

public static class Trace2
{
public const string SectionName = "trace2";
public const string EventTarget = "eventtarget";
public const string NormalTarget = "normaltarget";
}
}

public static class WindowsRegistry
Expand Down
19 changes: 19 additions & 0 deletions src/shared/Core/EnvironmentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ public interface IEnvironment
/// <param name="workingDirectory">Working directory for the new process.</param>
/// <returns><see cref="Process"/> object ready to start.</returns>
Process CreateProcess(string path, string args, bool useShellExecute, string workingDirectory);

/// <summary>
/// Set an environment variable at the specified target level.
/// </summary>
/// <param name="variable">Name of the environment variable to set.</param>
/// <param name="value">Value of the environment variable to set.</param>
/// <param name="target">Target level of environment variable to set (Machine, Process, or User).</param>
void SetEnvironmentVariable(string variable, string value,
EnvironmentVariableTarget target = EnvironmentVariableTarget.Process);
}

public abstract class EnvironmentBase : IEnvironment
Expand Down Expand Up @@ -141,6 +150,16 @@ internal virtual bool TryLocateExecutable(string program, ICollection<string> pa
path = null;
return false;
}

public void SetEnvironmentVariable(string variable, string value,
EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
{
if (Variables.Keys.Contains(variable)) return;
Environment.SetEnvironmentVariable(variable, value, target);
Variables = GetCurrentVariables();
}

protected abstract IReadOnlyDictionary<string, string> GetCurrentVariables();
}

public static class EnvironmentExtensions
Expand Down
10 changes: 10 additions & 0 deletions src/shared/Core/ITrace2Writer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace GitCredentialManager;

public interface ITrace2Writer : IDisposable
{
bool Failed { get; }

void Write(Trace2Message message);
}
Loading

0 comments on commit fb1942a

Please sign in to comment.