Skip to content

Commit

Permalink
Framework and runtime info (#526)
Browse files Browse the repository at this point in the history
Co-authored-by: Joao Grassi <[email protected]>
Co-authored-by: Anton Ovchinnikov <[email protected]>
  • Loading branch information
3 people authored Sep 24, 2020
1 parent 0afcf08 commit c8e014e
Show file tree
Hide file tree
Showing 15 changed files with 921 additions and 8 deletions.
4 changes: 2 additions & 2 deletions src/Sentry.NLog/SentryTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,14 @@ protected override void Write(LogEventInfo logEvent)
if (logEvent.Level >= Options.MinimumBreadcrumbLevel)
{
var breadcrumbFormatted = RenderLogEvent(BreadcrumbLayout, logEvent);
var breadcrumbCategory = RenderLogEvent(BreadcrumbCategory, logEvent);
string? breadcrumbCategory = RenderLogEvent(BreadcrumbCategory, logEvent);
if (string.IsNullOrEmpty(breadcrumbCategory))
{
breadcrumbCategory = null;
}

var message = string.IsNullOrWhiteSpace(breadcrumbFormatted)
? (exception?.Message ?? logEvent.FormattedMessage)
? exception?.Message ?? logEvent.FormattedMessage
: breadcrumbFormatted;

IDictionary<string, string>? data = null;
Expand Down
184 changes: 184 additions & 0 deletions src/Sentry/PlatformAbstractions/FrameworkInfo.NetFx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#if NETFX
using System;
using System.Collections.Generic;
using Microsoft.Win32;

namespace Sentry.PlatformAbstractions
{
/// <summary>
/// Information about .NET Framework in the running machine
/// </summary>
public static partial class FrameworkInfo
{
/// <summary>
/// Get the latest Framework installation for the specified CLR
/// </summary>
/// <remarks>
/// Supports the current 3 CLR versions:
/// CLR 1 => .NET 1.0, 1.1
/// CLR 2 => .NET 2.0, 3.0, 3.5
/// CLR 4 => .NET 4.0, 4.5.x, 4.6.x, 4.7.x
/// </remarks>
/// <param name="clrVersion">The CLR version: 1, 2 or 4</param>
/// <returns>The framework installation or null if none is found.</returns>
public static FrameworkInstallation? GetLatest(int clrVersion)
{
// CLR versions
// https://docs.microsoft.com/en-us/dotnet/standard/clr
if (clrVersion != 1 && clrVersion != 2 && clrVersion != 4)
{
return null;
}

if (clrVersion == 4)
{
var release = Get45PlusLatestInstallationFromRegistry();
if (release != null)
{
return new FrameworkInstallation
{
Version = GetNetFxVersionFromRelease(release.Value),
Release = release
};
}
}

FrameworkInstallation latest = null;
foreach (var installation in GetInstallations())
{
latest ??= installation;

if (clrVersion == 2)
{
// CLR 2 runs .NET 2 to 3.5
if ((installation.Version.Major == 2 || installation.Version.Major == 3)
&& installation.Version >= latest.Version)
{
latest = installation;
}
else
{
break;
}
}
else if (clrVersion == 4)
{
if (installation.Version.Major == 4
&& installation.Version >= latest.Version)
{
latest = installation;
}
else
{
break;
}
}
}

return latest;
}

/// <summary>
/// Get all .NET Framework installations in this machine
/// </summary>
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#to-find-net-framework-versions-by-querying-the-registry-in-code-net-framework-1-4"/>
/// <returns>Enumeration of installations</returns>
public static IEnumerable<FrameworkInstallation> GetInstallations()
{
using var ndpKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, string.Empty)
.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\");
if (ndpKey == null)
{
yield break;
}

foreach (var versionKeyName in ndpKey.GetSubKeyNames())
{
if (!versionKeyName.StartsWith("v") || !(ndpKey.OpenSubKey(versionKeyName) is RegistryKey versionKey))
{
continue;
}

var version = versionKey.GetString("Version");
if (version != null && versionKey.GetInt("Install") == 1)
{
// 1.0 to 3.5
Version.TryParse(version, out var parsed);
yield return new FrameworkInstallation
{
ShortName = versionKeyName,
Version = parsed,
ServicePack = versionKey.GetInt("SP")
};

continue;
}

// 4.0+
foreach (var subKeyName in versionKey.GetSubKeyNames())
{
var subKey = versionKey.OpenSubKey(subKeyName);
if (subKey?.GetInt("Install") != 1)
{
continue;
}

yield return GetFromV4(subKey, subKeyName);
}
}
}

private static FrameworkInstallation GetFromV4(RegistryKey subKey, string subKeyName)
{
var hasRelease = int.TryParse(
subKey.GetValue("Release", null)?.ToString(), out var release);

Version version = null;
if (hasRelease)
{
// 4.5+
var displayableVersion = GetNetFxVersionFromRelease(release);
if (displayableVersion != null)
{
version = displayableVersion;
}
}

if (version == null)
{
Version.TryParse(subKey.GetString("Version"), out var parsed);
version = parsed;
}

return new FrameworkInstallation
{
Profile = subKeyName switch
{
"Full" => FrameworkProfile.Full,
"Client" => FrameworkProfile.Client,
_ => null
},
Version = version,
ServicePack = subKey.GetInt("SP"),
Release = hasRelease ? release : null as int?
};
}

// https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#to-find-net-framework-versions-by-querying-the-registry-in-code-net-framework-45-and-later
internal static int? Get45PlusLatestInstallationFromRegistry()
{
using var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\");
return ndpKey?.GetInt("Release");
}

internal static Version GetNetFxVersionFromRelease(int release)
{
NetFxReleaseVersionMap.TryGetValue(release, out var version);
Version.TryParse(version, out var parsed);
return parsed;
}
}
}

#endif
29 changes: 29 additions & 0 deletions src/Sentry/PlatformAbstractions/FrameworkInfo.NetStandard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#if !NETFX

using System.Collections.Generic;
using System.Linq;

namespace Sentry.PlatformAbstractions
{
/// <summary>
/// No-op version for netstandard targets
/// </summary>
public static partial class FrameworkInfo
{
/// <summary>
/// No-op version for netstandard targets
/// </summary>
/// <param name="clr"></param>
/// <returns></returns>
public static FrameworkInstallation? GetLatest(int clr) => null;

/// <summary>
/// No-op version for netstandard targets
/// </summary>
/// <returns></returns>
public static IEnumerable<FrameworkInstallation> GetInstallations()
=> Enumerable.Empty<FrameworkInstallation>();
}
}

#endif
41 changes: 41 additions & 0 deletions src/Sentry/PlatformAbstractions/FrameworkInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;

namespace Sentry.PlatformAbstractions
{
/// <summary>
/// Information about .NET Framework in the running machine
/// The purpose of this partial class is to expose the API to all targets
/// For netstandard, the call to methods will be a simple no-op.
/// </summary>
public static partial class FrameworkInfo
{
/// <summary>
/// The map between release number and version number
/// </summary>
/// <see href="https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed" />
public static IReadOnlyDictionary<int, string> NetFxReleaseVersionMap { get; }
= new Dictionary<int, string>
{
{378389, "4.5"},
{378675, "4.5.1"},
{378758, "4.5.1"},
{379893, "4.5.2"},
{393295, "4.6"},
{393297, "4.6"},
{394254, "4.6.1"},
{394271, "4.6.1"},
{394802, "4.6.2"},
{394806, "4.6.2"},
{460798, "4.7"},
{460805, "4.7"},
{461308, "4.7.1"},
{461310, "4.7.1"},
{461808, "4.7.2"},
{461814, "4.7.2"},
{528040, "4.8"},
{528049, "4.8"},
{528209, "4.8"},
{528372, "4.8"},
};
}
}
75 changes: 75 additions & 0 deletions src/Sentry/PlatformAbstractions/FrameworkInstallation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;

namespace Sentry.PlatformAbstractions
{
/// <summary>
/// A .NET Framework installation
/// </summary>
/// <seealso href="https://en.wikipedia.org/wiki/.NET_Framework_version_history"/>
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed"/>
public class FrameworkInstallation
{
/// <summary>
/// Short name
/// </summary>
/// <example>
/// v2.0.50727, v3.5, v4.0
/// </example>
public string? ShortName { get; set; }
/// <summary>
/// Version
/// </summary>
/// <example>
/// 2.0.50727.4927, 3.0.30729.4926, 3.5.30729.4926
/// </example>
public Version? Version { get; set; }
/// <summary>
/// Service pack number, if any
/// </summary>
/// <remarks>
/// Only relevant prior to .NET 4
/// </remarks>
public int? ServicePack { get; set; }
/// <summary>
/// Type of Framework profile
/// </summary>
/// <remarks>Only relevant for .NET 3.5 and 4.0</remarks>
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/framework/deployment/client-profile"/>
public FrameworkProfile? Profile { get; set; }
/// <summary>
/// A .NET Framework release key
/// </summary>
/// <remarks>
/// Windows registry key:
/// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\Release
/// Only applicable when on Windows, with full .NET Framework 4.5 and later.
/// </remarks>
/// <see href="https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed"/>
public int? Release { get; set; }

/// <inheritdoc />
public override string ToString()
{
return Version?.Build > 0
? $"{Version.Major}.{Version.Minor}.{Version.Build}"
: $"{Version?.Major ?? 0}.{Version?.Minor ?? 0}";
}
}

/// <summary>
/// Type of Framework profile
/// </summary>
/// <remarks>Only relevant for .NET 3.5 and 4.0</remarks>
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/framework/deployment/client-profile"/>
public enum FrameworkProfile
{
/// <summary>
/// The .NET Client Profile is a subset of the .NET Framework
/// </summary>
Client,
/// <summary>
/// The full .NET Framework
/// </summary>
Full
}
}
15 changes: 15 additions & 0 deletions src/Sentry/PlatformAbstractions/RegistryKeyExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#if NETFX
using Microsoft.Win32;

namespace Sentry.PlatformAbstractions
{
internal static class RegistryKeyExtensions
{
public static string? GetString(this RegistryKey key, string value)
=> key.GetValue(value) as string;

public static int? GetInt(this RegistryKey key, string value)
=> (int?)key.GetValue(value);
}
}
#endif
Loading

0 comments on commit c8e014e

Please sign in to comment.