Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logging to DevHome #113

Merged
merged 3 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions DevHome.sln
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.SetupFlow.Configura
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.SetupFlow.DevDrive", "tools\SetupFlow\DevHome.SetupFlow.DevDrive\DevHome.SetupFlow.DevDrive.csproj", "{88E889AD-4F51-4312-8E16-5B81FA6BB1FD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Logging", "logging\DevHome.Logging.csproj", "{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -475,6 +477,22 @@ Global
{88E889AD-4F51-4312-8E16-5B81FA6BB1FD}.Release|x64.Build.0 = Release|x64
{88E889AD-4F51-4312-8E16-5B81FA6BB1FD}.Release|x86.ActiveCfg = Release|x86
{88E889AD-4F51-4312-8E16-5B81FA6BB1FD}.Release|x86.Build.0 = Release|x86
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|Any CPU.ActiveCfg = Debug|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|Any CPU.Build.0 = Debug|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|arm64.ActiveCfg = Debug|arm64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|arm64.Build.0 = Debug|arm64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|x64.ActiveCfg = Debug|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|x64.Build.0 = Debug|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|x86.ActiveCfg = Debug|x86
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Debug|x86.Build.0 = Debug|x86
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|Any CPU.ActiveCfg = Release|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|Any CPU.Build.0 = Release|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|arm64.ActiveCfg = Release|arm64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|arm64.Build.0 = Release|arm64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|x64.ActiveCfg = Release|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|x64.Build.0 = Release|x64
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|x86.ActiveCfg = Release|x86
{DB84FAA0-ACBF-48DA-AF1B-BCCA5053AF8B}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
15 changes: 15 additions & 0 deletions logging/DevHome.Logging.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>DevHome.Logging</RootNamespace>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230118.102" />
</ItemGroup>
</Project>
28 changes: 28 additions & 0 deletions logging/helpers/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

namespace DevHome.Logging.Helpers;

public static class DictionaryExtensions
{
public static void DisposeAll<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
if (dictionary is null)
{
throw new ArgumentNullException(nameof(dictionary));
}

foreach (var kv in dictionary)
{
if (kv.Key is IDisposable keyDisposable)
{
keyDisposable.Dispose();
}

if (kv.Value is IDisposable valDisposable)
{
valDisposable.Dispose();
}
}
}
}
35 changes: 35 additions & 0 deletions logging/helpers/FileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

namespace DevHome.Logging.Helpers;
public class FileSystem
{
public static string BuildOutputFilename(string filename, string outputFolder, bool createPathIfNecessary = true)
{
var outputFilename = SubstituteOutputFilename(filename, outputFolder);
var file = new FileInfo(outputFilename);
if (createPathIfNecessary)
{
file.Directory?.Create();
}

return file.FullName;
}

public static string SubstituteNow(string s)
{
if (s.Contains("{now}", StringComparison.CurrentCulture))
{
var now = DateTime.Now;
var nowAsString = $"{now:yyyyMMdd-HHmmss}";
return s.Replace("{now}", nowAsString);
}

return s;
}

public static string SubstituteOutputFilename(string filename, string outputDirectory)
{
return Path.Combine(SubstituteNow(outputDirectory), SubstituteNow(filename));
}
}
15 changes: 15 additions & 0 deletions logging/helpers/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

using System.Globalization;

namespace DevHome.Logging.Helpers;
public static class StringExtensions
{
public static string ToStringInvariant<T>(this T value) => Convert.ToString(value, CultureInfo.InvariantCulture)!;

public static string FormatInvariant(this string value, params object[] arguments)
{
return string.Format(CultureInfo.InvariantCulture, value, arguments);
}
}
37 changes: 37 additions & 0 deletions logging/listeners/DebugListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace DevHome.Logging.Listeners;
public class DebugListener : ListenerBase
{
public DebugListener(string name)
: base(name)
{
}

public override void HandleLogEvent(LogEvent evt)
{
// This listener does nothing unless a Debugger is attached.
// All events will be sent to the debugger.
if (Debugger.IsAttached)
{
DebugHandleLogEvent(evt);
}
}

private void DebugHandleLogEvent(LogEvent evt)
{
var sb = new StringBuilder();
sb.Append(CultureInfo.InvariantCulture, $"[{evt.FullSourceName}] {evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)}: {evt.Message}");
if (evt.Exception != null)
{
sb.Append(CultureInfo.InvariantCulture, $"{Environment.NewLine}{evt.Exception}");
}

Trace.WriteLine(sb.ToString());
}
}
8 changes: 8 additions & 0 deletions logging/listeners/DebugListenerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

namespace DevHome.Logging;
public partial class Options
{
public bool DebugListenerEnabled { get; set; } = true;
}
19 changes: 19 additions & 0 deletions logging/listeners/IListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

namespace DevHome.Logging.Listeners;
public interface IListener
{
string Name
{
get;
}

ILoggerHost? Host
{
get;
set;
}

void HandleLogEvent(LogEvent evt);
}
30 changes: 30 additions & 0 deletions logging/listeners/ListenerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

using DevHome.Logging;

namespace DevHome.Logging.Listeners;

public abstract class ListenerBase : IListener
{
public ILoggerHost? Host
{
get;
set;
}

public Options? Options => Host?.Options;

public string Name
{
get;
}

public ListenerBase(string name)
{
Host = null;
Name = name;
}

public abstract void HandleLogEvent(LogEvent evt);
}
98 changes: 98 additions & 0 deletions logging/listeners/LogFileListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

using System.Globalization;

namespace DevHome.Logging.Listeners;
public class LogFileListener : ListenerBase, IDisposable
{
private readonly TextWriter? writer;

public LogFileListener(string name, string filename)
: base(name)
{
// Should handle locked file situation better.
// For now assume one process is writing each file.
// And fail silently if we can't write for whatever reason.
try
{
writer = new StreamWriter(filename, true);
}
catch (IOException)
{
// Do nothing, we don't want to crash the program because
// the log file couldn't be written, carry on without it.
}
}

public override void HandleLogEvent(LogEvent evt)
{
HandleLogFileEvent(evt, true);
}

private void HandleLogFileEvent(LogEvent evt, bool newline)
{
HandleLogFileEvent(evt, newline, LogEvent.NoElapsed);
}

private void HandleLogFileEvent(LogEvent evt, bool newline, TimeSpan elapsed)
{
if (!MeetsFilter(evt))
{
return;
}

writer?.Write($"[{DateTime.UtcNow:yyyy/MM/dd hh\\:mm\\:ss\\.ffff}][{evt.FullSourceName}] {evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)}: {evt.Message}");
if (elapsed != LogEvent.NoElapsed)
{
writer?.Write($" [Elapsed: {elapsed:hh\\:mm\\:ss\\.ffffff}]");
}

if (evt.Exception != null)
{
WriteLine(newline);
writer?.Write(evt?.Exception.ToString());
}

if (newline)
{
writer?.WriteLine();
}

writer?.Flush();
}

private void WriteLine(bool newline)
{
if (newline)
{
writer?.WriteLine();
}
}

private bool MeetsFilter(LogEvent evt) => evt?.Severity >= Options?.LogFileFilter;

private bool disposed; // To detect redundant calls

protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}

if (disposing)
{
writer?.Dispose();
}

disposed = true;
}

public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
}
25 changes: 25 additions & 0 deletions logging/listeners/LogFileListenerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

namespace DevHome.Logging;
public partial class Options
{
private const string LogFileNameDefault = "PG.log";
private const string LogFileFolderNameDefault = "{now}";

public string LogFileName { get; set; } = LogFileNameDefault;

public string LogFileFolderName { get; set; } = LogFileFolderNameDefault;

// The Temp Path is used for storage by default so tests can run this code without being packaged.
// If we directly put in the ApplicationData folder, it would fail anytime the program was not packaged.
// For use with packaged application, set in Options to:
// ApplicationData.Current.TemporaryFolder.Path
public string LogFileFolderRoot { get; set; } = Path.GetTempPath();

public string LogFileFolderPath => Path.Combine(LogFileFolderRoot, LogFileFolderName);

public bool LogFileEnabled { get; set; } = true;

public SeverityLevel LogFileFilter { get; set; } = SeverityLevel.Info;
}
Loading