Fix "runtime description" used for internal analytics (#519)
* remove use of RuntimeInformation in NativeMethods and ContainerInfo * add new FrameworkDescription class * use new FrameworkDescription class from Api class to set http headers * clean up xml-doc comments * add "OS" to Resharper's spell-check abbreviations * change Resharper's editor style: don't _always_ chop a ternary expression into multiple lines * revert change to nuget package reference * refactor code into GetVersionFromAssemblyAttributes() * make nuget reference private and add comment * remove unnecessary call to GetTypeInfo() * coalesce more null values to "unknown" * add comment * move the "try/catch" inside the "if" instead of the other way around * remove try/catch, update comment * update log messages * simplify ToString()
This commit is contained in:
parent
07e420f92d
commit
fb0ac61098
|
|
@ -35,7 +35,7 @@
|
|||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_TERNARY_EXPR_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CppUseAuto/AutoWasChosen/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UpdateExternalUsagesOnly/@EntryValue">False</s:Boolean>
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Async/@EntryIndexedValue">True</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNamingAutoDetectionCompleted/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// ReSharper disable MemberHidesStaticFromOuterClass
|
||||
|
|
@ -5,9 +6,11 @@ namespace Datadog.Trace.ClrProfiler
|
|||
{
|
||||
internal static class NativeMethods
|
||||
{
|
||||
private static readonly bool IsWindows = string.Equals(FrameworkDescription.Create().OSPlatform, "Windows", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsProfilerAttached()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (IsWindows)
|
||||
{
|
||||
return Windows.IsProfilerAttached();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Datadog.Trace.Containers;
|
||||
using Datadog.Trace.Logging;
|
||||
|
|
@ -15,7 +14,7 @@ namespace Datadog.Trace.Agent
|
|||
{
|
||||
private const string TracesPath = "/v0.4/traces";
|
||||
|
||||
private static readonly ILog Log = LogProvider.For<Api>();
|
||||
private static readonly ILog Log = LogProvider.GetCurrentClassLogger();
|
||||
private static readonly SerializationContext SerializationContext = new SerializationContext();
|
||||
private static readonly SpanMessagePackSerializer Serializer = new SpanMessagePackSerializer(SerializationContext);
|
||||
|
||||
|
|
@ -41,20 +40,31 @@ namespace Datadog.Trace.Agent
|
|||
|
||||
_tracesEndpoint = new Uri(baseEndpoint, TracesPath);
|
||||
|
||||
GetFrameworkDescription(out string frameworkName, out string frameworkVersion);
|
||||
var tracerVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
var containerId = ContainerInfo.GetContainerId();
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.Language, ".NET");
|
||||
|
||||
// report runtime details
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.Language, ".NET");
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageInterpreter, frameworkName);
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageVersion, frameworkVersion);
|
||||
try
|
||||
{
|
||||
var frameworkDescription = FrameworkDescription.Create();
|
||||
|
||||
if (frameworkDescription != null)
|
||||
{
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageInterpreter, frameworkDescription.Name);
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageVersion, frameworkDescription.ProductVersion);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting framework description", e);
|
||||
}
|
||||
|
||||
// report Tracer version
|
||||
var tracerVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.TracerVersion, tracerVersion);
|
||||
|
||||
// report container id (only Linux containers supported for now)
|
||||
var containerId = ContainerInfo.GetContainerId();
|
||||
|
||||
if (containerId != null)
|
||||
{
|
||||
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.ContainerId, containerId);
|
||||
|
|
@ -145,20 +155,6 @@ namespace Datadog.Trace.Agent
|
|||
return uniqueTraceIds;
|
||||
}
|
||||
|
||||
private static void GetFrameworkDescription(out string name, out string version)
|
||||
{
|
||||
// RuntimeInformation.FrameworkDescription returns string like ".NET Framework 4.7.2" or ".NET Core 2.1",
|
||||
// we want to split the runtime from the version so we can report them as separate values
|
||||
string frameworkDescription = RuntimeInformation.FrameworkDescription;
|
||||
int index = RuntimeInformation.FrameworkDescription.LastIndexOf(' ');
|
||||
|
||||
// everything before the last space
|
||||
name = frameworkDescription.Substring(0, index).Trim();
|
||||
|
||||
// everything after the last space
|
||||
version = frameworkDescription.Substring(index).Trim();
|
||||
}
|
||||
|
||||
internal class ApiResponse
|
||||
{
|
||||
[JsonProperty("rate_by_service")]
|
||||
|
|
|
|||
|
|
@ -15,13 +15,11 @@ namespace Datadog.Trace
|
|||
|
||||
/// <summary>
|
||||
/// The interpreter for the given language, e.g. ".NET Framework" or ".NET Core".
|
||||
/// The value of <see cref="RuntimeInformation.FrameworkDescription"/>.
|
||||
/// </summary>
|
||||
public const string LanguageInterpreter = "Datadog-Meta-Lang-Interpreter";
|
||||
|
||||
/// <summary>
|
||||
/// The interpreter version for the given language, e.g. "4.7.2" for .NET Framework or "2.1" for .NET Core.
|
||||
/// The value of <see cref="RuntimeInformation.FrameworkDescription"/>.
|
||||
/// </summary>
|
||||
public const string LanguageVersion = "Datadog-Meta-Lang-Version";
|
||||
|
||||
|
|
|
|||
|
|
@ -59,21 +59,12 @@ namespace Datadog.Trace.Containers
|
|||
|
||||
private static string GetContainerIdInternal()
|
||||
{
|
||||
bool isLinux;
|
||||
|
||||
try
|
||||
{
|
||||
isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WarnException("Unable to determine OS. Will not report container id.", ex);
|
||||
return null;
|
||||
}
|
||||
var isLinux = string.Equals(FrameworkDescription.Create().OSPlatform, "Linux", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
try
|
||||
{
|
||||
if (isLinux && File.Exists(ControlGroupsFilePath))
|
||||
if (isLinux &&
|
||||
File.Exists(ControlGroupsFilePath))
|
||||
{
|
||||
var lines = File.ReadLines(ControlGroupsFilePath);
|
||||
return ParseCgroupLines(lines);
|
||||
|
|
@ -82,7 +73,6 @@ namespace Datadog.Trace.Containers
|
|||
catch (Exception ex)
|
||||
{
|
||||
Log.WarnException("Error reading cgroup file. Will not report container id.", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- NuGet -->
|
||||
|
|
@ -13,10 +13,16 @@
|
|||
<Reference Include="System.Web" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<!--
|
||||
This reference allows us to build the code without precompiler directives,
|
||||
but the logic at runtime will never try to use the Registry if it's not available
|
||||
-->
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LibLog" Version="5.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LibLog" Version="5.0.6" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
|
|
@ -24,4 +30,4 @@
|
|||
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Datadog.Trace.Logging;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Datadog.Trace
|
||||
{
|
||||
internal class FrameworkDescription
|
||||
{
|
||||
private static readonly ILog Log = LogProvider.GetCurrentClassLogger();
|
||||
|
||||
private static readonly Assembly RootAssembly = typeof(object).Assembly;
|
||||
|
||||
private static readonly Tuple<int, string>[] DotNetFrameworkVersionMapping =
|
||||
{
|
||||
// highest known value is 528049
|
||||
Tuple.Create(528050, "4.8+"),
|
||||
|
||||
// known min value for each framework version
|
||||
Tuple.Create(528040, "4.8"),
|
||||
Tuple.Create(461808, "4.7.2"),
|
||||
Tuple.Create(461308, "4.7.1"),
|
||||
Tuple.Create(460798, "4.7"),
|
||||
Tuple.Create(394802, "4.6.2"),
|
||||
Tuple.Create(394254, "4.6.1"),
|
||||
Tuple.Create(393295, "4.6"),
|
||||
Tuple.Create(379893, "4.5.2"),
|
||||
Tuple.Create(378675, "4.5.1"),
|
||||
Tuple.Create(378389, "4.5"),
|
||||
};
|
||||
|
||||
private FrameworkDescription(
|
||||
string name,
|
||||
string productVersion,
|
||||
string osPlatform,
|
||||
string osArchitecture,
|
||||
string processArchitecture)
|
||||
{
|
||||
Name = name;
|
||||
ProductVersion = productVersion;
|
||||
OSPlatform = osPlatform;
|
||||
OSArchitecture = osArchitecture;
|
||||
ProcessArchitecture = processArchitecture;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string ProductVersion { get; }
|
||||
|
||||
public string OSPlatform { get; }
|
||||
|
||||
public string OSArchitecture { get; }
|
||||
|
||||
public string ProcessArchitecture { get; }
|
||||
|
||||
public static FrameworkDescription Create()
|
||||
{
|
||||
var assemblyName = RootAssembly.GetName();
|
||||
|
||||
if (string.Equals(assemblyName.Name, "mscorlib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// .NET Framework
|
||||
return new FrameworkDescription(
|
||||
".NET Framework",
|
||||
GetNetFrameworkVersion() ?? "unknown",
|
||||
"Windows",
|
||||
Environment.Is64BitOperatingSystem ? "x64" : "x86",
|
||||
Environment.Is64BitProcess ? "x64" : "x86");
|
||||
}
|
||||
|
||||
// .NET Core
|
||||
return CreateFromRuntimeInformation();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// examples:
|
||||
// .NET Framework 4.8 x86 on Windows x64
|
||||
// .NET Core 3.0.0 x64 on Linux x64
|
||||
return $"{Name} {ProductVersion} {ProcessArchitecture} on {OSPlatform} {OSArchitecture}";
|
||||
}
|
||||
|
||||
private static FrameworkDescription CreateFromRuntimeInformation()
|
||||
{
|
||||
string frameworkName = null;
|
||||
string osPlatform = null;
|
||||
|
||||
try
|
||||
{
|
||||
// RuntimeInformation.FrameworkDescription returns a string like ".NET Framework 4.7.2" or ".NET Core 2.1",
|
||||
// we want to return everything before the last space
|
||||
string frameworkDescription = RuntimeInformation.FrameworkDescription;
|
||||
int index = frameworkDescription.LastIndexOf(' ');
|
||||
frameworkName = frameworkDescription.Substring(0, index).Trim();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting framework name from RuntimeInformation", e);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
osPlatform = "Windows";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
|
||||
{
|
||||
osPlatform = "Linux";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
|
||||
{
|
||||
osPlatform = "MacOS";
|
||||
}
|
||||
|
||||
return new FrameworkDescription(
|
||||
frameworkName ?? "unknown",
|
||||
GetNetCoreVersion() ?? "unknown",
|
||||
osPlatform ?? "unknown",
|
||||
RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(),
|
||||
RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
private static string GetNetFrameworkVersion()
|
||||
{
|
||||
string productVersion = null;
|
||||
|
||||
try
|
||||
{
|
||||
object registryValue;
|
||||
|
||||
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default))
|
||||
using (var subKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
|
||||
{
|
||||
registryValue = subKey?.GetValue("Release");
|
||||
}
|
||||
|
||||
if (registryValue is int release)
|
||||
{
|
||||
// find the known version on the list with the largest release number
|
||||
// that is lower than or equal to the release number in the Windows Registry
|
||||
productVersion = DotNetFrameworkVersionMapping.FirstOrDefault(t => release >= t.Item1)?.Item2;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting .NET Framework version from Windows Registry", e);
|
||||
}
|
||||
|
||||
if (productVersion == null)
|
||||
{
|
||||
// if we fail to extract version from assembly path,
|
||||
// fall back to the [AssemblyInformationalVersion] or [AssemblyFileVersion]
|
||||
productVersion = GetVersionFromAssemblyAttributes();
|
||||
}
|
||||
|
||||
return productVersion;
|
||||
}
|
||||
|
||||
private static string GetNetCoreVersion()
|
||||
{
|
||||
string productVersion = null;
|
||||
|
||||
if (Environment.Version.Major == 3 || Environment.Version.Major >= 5)
|
||||
{
|
||||
// Environment.Version returns "4.x" in .NET Core 2.x,
|
||||
// but it is correct since .NET Core 3.0.0
|
||||
productVersion = Environment.Version.ToString();
|
||||
}
|
||||
|
||||
if (productVersion == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// try to get product version from assembly path
|
||||
Match match = Regex.Match(
|
||||
RootAssembly.CodeBase,
|
||||
@"/[^/]*microsoft\.netcore\.app/(\d+\.\d+\.\d+[^/]*)/",
|
||||
RegexOptions.IgnoreCase);
|
||||
|
||||
if (match.Success && match.Groups.Count > 0 && match.Groups[1].Success)
|
||||
{
|
||||
productVersion = match.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting .NET Core version from assembly path", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (productVersion == null)
|
||||
{
|
||||
// if we fail to extract version from assembly path,
|
||||
// fall back to the [AssemblyInformationalVersion] or [AssemblyFileVersion]
|
||||
productVersion = GetVersionFromAssemblyAttributes();
|
||||
}
|
||||
|
||||
if (productVersion == null)
|
||||
{
|
||||
// at this point, everything else has failed (this is probably the same as [AssemblyFileVersion] above)
|
||||
productVersion = Environment.Version.ToString();
|
||||
}
|
||||
|
||||
return productVersion;
|
||||
}
|
||||
|
||||
private static string GetVersionFromAssemblyAttributes()
|
||||
{
|
||||
string productVersion = null;
|
||||
|
||||
try
|
||||
{
|
||||
// if we fail to extract version from assembly path, fall back to the [AssemblyInformationalVersion],
|
||||
var informationalVersionAttribute = (AssemblyInformationalVersionAttribute)RootAssembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute));
|
||||
|
||||
// split remove the commit hash from pre-release versions
|
||||
productVersion = informationalVersionAttribute?.InformationalVersion?.Split('+')[0];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting framework version from [AssemblyInformationalVersion]", e);
|
||||
}
|
||||
|
||||
if (productVersion == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// and if that fails, try [AssemblyFileVersion]
|
||||
var fileVersionAttribute = (AssemblyFileVersionAttribute)RootAssembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute));
|
||||
productVersion = fileVersionAttribute?.Version;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error getting framework version from [AssemblyFileVersion]", e);
|
||||
}
|
||||
}
|
||||
|
||||
return productVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue