diff --git a/Datadog.Trace.sln.DotSettings b/Datadog.Trace.sln.DotSettings index def04568a..725bd2cf4 100644 --- a/Datadog.Trace.sln.DotSettings +++ b/Datadog.Trace.sln.DotSettings @@ -35,7 +35,7 @@ CHOP_IF_LONG CHOP_IF_LONG False - CHOP_ALWAYS + False True False @@ -43,6 +43,7 @@ True True True + OS True <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> diff --git a/src/Datadog.Trace.ClrProfiler.Managed/NativeMethods.cs b/src/Datadog.Trace.ClrProfiler.Managed/NativeMethods.cs index 366f25cc9..9ab64e6f1 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/NativeMethods.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/NativeMethods.cs @@ -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(); } diff --git a/src/Datadog.Trace/Agent/Api.cs b/src/Datadog.Trace/Agent/Api.cs index 0f081da53..b76650791 100644 --- a/src/Datadog.Trace/Agent/Api.cs +++ b/src/Datadog.Trace/Agent/Api.cs @@ -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(); + 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")] diff --git a/src/Datadog.Trace/AgentHttpHeaderNames.cs b/src/Datadog.Trace/AgentHttpHeaderNames.cs index b84dae8e5..4d8dea176 100644 --- a/src/Datadog.Trace/AgentHttpHeaderNames.cs +++ b/src/Datadog.Trace/AgentHttpHeaderNames.cs @@ -15,13 +15,11 @@ namespace Datadog.Trace /// /// The interpreter for the given language, e.g. ".NET Framework" or ".NET Core". - /// The value of . /// public const string LanguageInterpreter = "Datadog-Meta-Lang-Interpreter"; /// /// The interpreter version for the given language, e.g. "4.7.2" for .NET Framework or "2.1" for .NET Core. - /// The value of . /// public const string LanguageVersion = "Datadog-Meta-Lang-Version"; diff --git a/src/Datadog.Trace/Containers/ContainerInfo.cs b/src/Datadog.Trace/Containers/ContainerInfo.cs index 3de0e09ac..08e44d7cb 100644 --- a/src/Datadog.Trace/Containers/ContainerInfo.cs +++ b/src/Datadog.Trace/Containers/ContainerInfo.cs @@ -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; diff --git a/src/Datadog.Trace/Datadog.Trace.csproj b/src/Datadog.Trace/Datadog.Trace.csproj index 3b55d1c62..1cd088bd6 100644 --- a/src/Datadog.Trace/Datadog.Trace.csproj +++ b/src/Datadog.Trace/Datadog.Trace.csproj @@ -1,4 +1,4 @@ - + @@ -13,10 +13,16 @@ + + + + + - - all - + @@ -24,4 +30,4 @@ - + \ No newline at end of file diff --git a/src/Datadog.Trace/FrameworkDescription.cs b/src/Datadog.Trace/FrameworkDescription.cs new file mode 100644 index 000000000..6329aef79 --- /dev/null +++ b/src/Datadog.Trace/FrameworkDescription.cs @@ -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[] 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; + } + } +}