Skip to content

Add ExceptionlessWindowsEnvironmentInfoCollector #270

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

Merged
merged 7 commits into from
May 2, 2022
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
53 changes: 28 additions & 25 deletions src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@
namespace Exceptionless.Services {
public class DefaultEnvironmentInfoCollector : IEnvironmentInfoCollector {
private static EnvironmentInfo _environmentInfo;
private readonly ExceptionlessConfiguration _config;
private readonly IExceptionlessLog _log;
protected ExceptionlessConfiguration Config { get; }
protected IExceptionlessLog Log { get; }

public DefaultEnvironmentInfoCollector(ExceptionlessConfiguration config, IExceptionlessLog log) {
_config = config;
_log = log;
Config = config;
Log = log;
}

public EnvironmentInfo GetEnvironmentInfo() {
public virtual EnvironmentInfo GetEnvironmentInfo() {
if (_environmentInfo != null) {
PopulateThreadInfo(_environmentInfo);
PopulateMemoryInfo(_environmentInfo);
return _environmentInfo;
}

var info = new EnvironmentInfo();
PopulateApplicationInfo(info);
PopulateRuntimeInfo(info);
PopulateProcessInfo(info);
PopulateThreadInfo(info);
Expand All @@ -42,16 +43,16 @@ private void PopulateApplicationInfo(EnvironmentInfo info) {
try {
info.Data.Add("AppDomainName", AppDomain.CurrentDomain.FriendlyName);
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get AppDomain friendly name. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get AppDomain friendly name. Error message: {0}", ex.Message);
}

if (_config.IncludeIpAddress) {
if (Config.IncludeIpAddress) {
try {
IPHostEntry hostEntry = Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false).GetAwaiter().GetResult();
if (hostEntry != null && hostEntry.AddressList.Any())
info.IpAddress = String.Join(", ", hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).Select(a => a.ToString()).ToArray());
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get ip address. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get ip address. Error message: {0}", ex.Message);
}
}
}
Expand All @@ -60,44 +61,46 @@ private void PopulateProcessInfo(EnvironmentInfo info) {
try {
info.ProcessorCount = Environment.ProcessorCount;
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get processor count. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get processor count. Error message: {0}", ex.Message);
}

try {
Process process = Process.GetCurrentProcess();
info.ProcessName = process.ProcessName;
info.ProcessId = process.Id.ToString(NumberFormatInfo.InvariantInfo);
using (Process process = Process.GetCurrentProcess()) {
info.ProcessName = process.ProcessName;
info.ProcessId = process.Id.ToString(NumberFormatInfo.InvariantInfo);
}
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process name or id. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process name or id. Error message: {0}", ex.Message);
}

try {
info.CommandLine = Environment.CommandLine;
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get command line. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get command line. Error message: {0}", ex.Message);
}
}

private void PopulateThreadInfo(EnvironmentInfo info) {
try {
info.ThreadId = Thread.CurrentThread.ManagedThreadId.ToString(NumberFormatInfo.InvariantInfo);
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get thread id. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get thread id. Error message: {0}", ex.Message);
}

try {
info.ThreadName = Thread.CurrentThread.Name;
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get current thread name. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get current thread name. Error message: {0}", ex.Message);
}
}

private void PopulateMemoryInfo(EnvironmentInfo info) {
try {
Process process = Process.GetCurrentProcess();
info.ProcessMemorySize = process.PrivateMemorySize64;
using (Process process = Process.GetCurrentProcess()) {
info.ProcessMemorySize = process.PrivateMemorySize64;
}
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process memory size. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process memory size. Error message: {0}", ex.Message);
}

#if NET45
Expand All @@ -116,7 +119,7 @@ private void PopulateMemoryInfo(EnvironmentInfo info) {
info.AvailablePhysicalMemory = Convert.ToInt64(computerInfo.AvailablePhysicalMemory);
}
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get physical memory. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get physical memory. Error message: {0}", ex.Message);
}
#endif
}
Expand All @@ -142,11 +145,11 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
info.Data["ProcessArchitecture"] = RuntimeInformation.ProcessArchitecture.ToString();
#endif

if (_config.IncludeMachineName) {
if (Config.IncludeMachineName) {
try {
info.MachineName = Environment.MachineName;
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get machine name. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get machine name. Error message: {0}", ex.Message);
}
}

Expand All @@ -163,7 +166,7 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
computerInfo = new Microsoft.VisualBasic.Devices.ComputerInfo();
#endif
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get computer info. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get computer info. Error message: {0}", ex.Message);
}

if (computerInfo == null)
Expand All @@ -182,7 +185,7 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
info.Architecture = Is64BitOperatingSystem() ? "x64" : "x86";
#endif
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get populate runtime info. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get populate runtime info. Error message: {0}", ex.Message);
}
}

Expand Down Expand Up @@ -225,7 +228,7 @@ private bool Is64BitOperatingSystem() {

return ((methodExist && KernelNativeMethods.IsWow64Process(KernelNativeMethods.GetCurrentProcess(), out is64)) && is64);
} catch (Exception ex) {
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get CPU architecture. Error message: {0}", ex.Message);
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get CPU architecture. Error message: {0}", ex.Message);
}

return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Diagnostics;
using Exceptionless.Logging;
using Exceptionless.Models.Data;
using Exceptionless.Services;

namespace Exceptionless {
public class ExceptionlessWindowsEnvironmentInfoCollector : DefaultEnvironmentInfoCollector {
public ExceptionlessWindowsEnvironmentInfoCollector(ExceptionlessConfiguration config, IExceptionlessLog log) : base(config, log) { }

public override EnvironmentInfo GetEnvironmentInfo() {
EnvironmentInfo info = base.GetEnvironmentInfo();
PopulateHandleInfo(info);
return info;
}

private void PopulateHandleInfo(EnvironmentInfo info) {
try {
using (Process currentProcess = Process.GetCurrentProcess()) {
info.Data["Handles"] = currentProcess.HandleCount;
info.Data["UserObjects"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.UserObjectCount);
info.Data["GDIObjects"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.GDIObjectCount);
info.Data["UserObjectsPeak"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.UserObjectsPeakCount);
info.Data["GDIObjectsPeak"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.GDIObjectsPeakCount);
}
}
catch (Exception ex) {
Log.FormattedWarn(typeof(ExceptionlessWindowsEnvironmentInfoCollector), "Unable to get process handles, User objects, or GDI objects. Error message: {0}", ex.Message);
}
}

private static class User32NativeMethods {
#region User32

/// <summary>
/// <para>
/// Retrieves the count of handles to graphical user interface
/// (GUI) objects in use by the specified process.
/// </para>
/// API reference: <see href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getguiresources"/>
/// </summary>
/// <param name="hProcess"></param>
/// <param name="uiFlags"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern int GetGuiResources(IntPtr hProcess, uint uiFlags);

#endregion

/// <summary>
/// Enum representing the possible values to pass to <see cref="GetGuiResources(IntPtr, UInt32)"/>.
/// </summary>
public enum UIFlags {
/// <summary>
/// Return the count of GDI objects.
/// </summary>
GDIObjectCount = 0,
/// <summary>
/// Return the count of USER objects.
/// </summary>
UserObjectCount = 1,
/// <summary>
/// Return the peak count of GDI objects.
/// </summary>
GDIObjectsPeakCount = 2,
/// <summary>
/// Return the peak count of USER objects.
/// </summary>
UserObjectsPeakCount = 4,
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Exceptionless.Dialogs;
using Exceptionless.Logging;
using Exceptionless.Plugins.Default;
using Exceptionless.Services;
using Exceptionless.Windows.Extensions;

namespace Exceptionless {
Expand All @@ -18,6 +19,7 @@ public static void Register(this ExceptionlessClient client, bool showDialog = t
throw new ArgumentNullException(nameof(client));

client.Configuration.AddPlugin<SetEnvironmentUserPlugin>();
client.Configuration.Resolver.Register<IEnvironmentInfoCollector, ExceptionlessWindowsEnvironmentInfoCollector>();
client.Startup();

client.RegisterApplicationThreadExceptionHandler();
Expand Down