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

feat: #16 Include host machine environment details #55

Merged
merged 8 commits into from
Oct 16, 2024
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
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,61 @@ This method accepts the following arguments:
- `category`: A custom value used to arbitrarily group this Breadcrumb.
- `customData`: Any custom data you want to record about application state when the Breadcrumb was recorded.

### Environment details

Raygun for Blazor captures environment details differently depending on the platform where the error originated.

The availability of each environment detail properties depend on the platform itself, so some of them may not be always available.

For more information, you can check the `EnvironmentDetails.cs` file.

#### Browser environment details

When the application is running on a browser, for example when using Blazor WebAssembly, or when running MAUI Blazor Hybrid applications on mobile, the attached environment details are obtained from the browser layer on the client side.

For example:

```json
"environment": {
"browser-Height": 982,
"browserName": "Mozilla",
"browser-Width": 1512,
"color-Depth": 24,
// etc ...
}
```

#### Server environment details

When the application is running on a server machine, for example when using Blazor Server, or when running MAUI Blazor Hybrid applications on desktop, the attached environment details are obtained from the operating system of the running platform.

For example:

```json
"environment": {
"architecture": "X64",
"availablePhysicalMemory": 1,
"cpu": "X64",
"deviceName": "WINDOWS-PC",
// etc ...
}
```

The client side environment details from the browser, if available, will be also attached as part of the **User Custom Data** under `BrowserEnvironment`:

```json
"userCustomData": {
"BrowserEnvironment": {
"browser-Height": 1392,
"browser": "Google",
"browserName": "Chrome",
"browser-Version": "129.0.6668.100",
"browser-Width": 2560,
// etc ...
}
}
```

### Attaching user details

Raygun for Blazor provides two ways to attach user details to error reports:
Expand Down
58 changes: 58 additions & 0 deletions src/Raygun.Blazor/Models/EnvironmentDetails.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -207,6 +208,63 @@ internal EnvironmentDetails(BrowserSpecs? specs, BrowserStats? stats)
BrowserVersion = specs?.UAHints?.ComponentVersions?[uaBrowserVersionKey] ?? specs?.CalculatedBrowserVersion;
}

/// <summary>
/// Creates a new instance of the <see cref="EnvironmentDetails" /> using runtime information.
/// </summary>
/// <remarks>
/// Some of the properties are not available in all environments.
/// e.g. Browsers, Android and iOS cannot access Process information.
/// </remarks>
/// <returns>
/// Environment details for the current runtime.
/// </returns>
static internal EnvironmentDetails FromRuntime() => new()
{
// In most cases the Architecture and the Cpu will be the same.
// The "ProcessArchitecture" is defined at compile time.
Architecture = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString(),
// The "OSArchitecture" is taken from the OS.
Cpu = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture.ToString(),

// The DeviceName is the machine name.
// Couldn't find a way to obtain Model or Manufacturer.
DeviceName = Environment.MachineName,

DiskSpaceFree = GetDiskSpaceFree(),

Locale = System.Globalization.CultureInfo.CurrentCulture.Name,
OSVersion = Environment.OSVersion.Version.ToString(),
Platform = Environment.OSVersion.Platform.ToString(),
ProcessorCount = Environment.ProcessorCount,
UtcOffset = (int)DateTimeOffset.Now.Offset.TotalHours,

// Disable warning on platform compatibility: Process not available on all platforms.
#pragma warning disable CA1416 // Validate platform compatibility
// Memory values obtained in Bytes and must be converted to Megabytes
// Working Set: The amount of physical memory, in bytes, allocated for the associated process.
// See: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.workingset64?view=net-8.0&redirectedfrom=MSDN#System_Diagnostics_Process_WorkingSet64
TotalPhysicalMemory = Convert.ToUInt64(Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024),
// Virtual Memory Size: Gets the amount of the virtual memory, in bytes, allocated for the associated process.
TotalVirtualMemory = Convert.ToUInt64(Process.GetCurrentProcess().VirtualMemorySize64 / 1024 / 1024),
#pragma warning restore CA1416 // Validate platform compatibility
};

static private List<double> GetDiskSpaceFree()
{
try
{
// Convert Bytes to Gygabytes
// Each drive is listed individually
return System.IO.DriveInfo.GetDrives().Select(d => Convert.ToDouble(d.TotalFreeSpace / 1024 / 1024 / 1024)).ToList();
}
catch (Exception)
{
// If we can't get the disk space, return an empty list.
// e.g. "System.IO.IOException: The device is not ready" when running on CI.
return [];
}
}

#endregion

}
Expand Down
3 changes: 2 additions & 1 deletion src/Raygun.Blazor/Models/EventDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Raygun.Blazor.Models
{


/// <summary>
///
/// </summary>
Expand Down Expand Up @@ -86,7 +87,7 @@ public record EventDetails
/// These will be searchable on the dashboard.
/// </remarks>
[JsonInclude]
public Dictionary<string, string>? UserCustomData { get; set; }
public Dictionary<string, object>? UserCustomData { get; set; }

#endregion

Expand Down
25 changes: 23 additions & 2 deletions src/Raygun.Blazor/RaygunBlazorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
Expand Down Expand Up @@ -194,7 +195,7 @@ public void RecordBreadcrumb(string? message, BreadcrumbType breadcrumbType = Br
public async Task RecordExceptionAsync(Exception ex,
UserDetails? userDetails = null,
List<string>? tags = null,
Dictionary<string, string>? userCustomData = null,
Dictionary<string, object>? userCustomData = null,
CancellationToken cancellationToken = default)
{
_raygunLogger?.Verbose("[RaygunBlazorClient] Recording exception: " + ex);
Expand All @@ -216,12 +217,32 @@ public async Task RecordExceptionAsync(Exception ex,
user = await _userManager!.GetCurrentUser();
}

EnvironmentDetails? environment;
if (OperatingSystem.IsBrowser() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid())
{
// If running on browser (e.g. WebAssembly)
// or Mobile MAUI Blazor Hybrid apps (iOS or Android),
// obtain environment details from the browser
environment = await _browserInterop.GetBrowserEnvironment();
}
else
{
// If running on Server (Linux, Windows, MacOS, etc.)
// or Desktop MAUI Hybrid apps (Windows or Mac Catalyst),
// obtain environment details from the runtime
environment = EnvironmentDetails.FromRuntime();
// Add user browser details to userCustomData
userCustomData ??= [];
userCustomData.Add("BrowserEnvironment", await _browserInterop.GetBrowserEnvironment());
}


var request = new RaygunRequest
{
Details = new EventDetails(appVersion)
{
Breadcrumbs = _breadcrumbs.ToList(),
Environment = await _browserInterop.GetBrowserEnvironment(),
Environment = environment,
Error = new ErrorDetails(ex),
Tags = tags,
User = user,
Expand Down
6 changes: 3 additions & 3 deletions src/Raygun.Blazor/RaygunBrowserInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class RaygunBrowserInterop : IAsyncDisposable
private readonly IRaygunLogger? _raygunLogger;
private readonly IWindowService _windowService;
private Action<string, BreadcrumbType, string?, Dictionary<string, object>?, string?>? _breadcrumbAction;
private Func<Exception, UserDetails?, List<string>?, Dictionary<string, string>?, CancellationToken, Task>? _exceptionAction;
private Func<Exception, UserDetails?, List<string>?, Dictionary<string, object>?, CancellationToken, Task>? _exceptionAction;

#endregion

Expand Down Expand Up @@ -119,7 +119,7 @@ public ValueTask RecordJsBreadcrumb(string message, BreadcrumbType breadcrumbTyp
/// <param name="customData"></param>
/// <returns></returns>
[JSInvokable]
public async ValueTask RecordJsException(JSError error, List<string>? tags = null, Dictionary<string, string>? customData = null)
public async ValueTask RecordJsException(JSError error, List<string>? tags = null, Dictionary<string, object>? customData = null)
{
_raygunLogger?.Verbose("[RaygunBrowserInterop] Recording JS exception: " + error.Message);
var exception = new WebIDLException($"{error.Name}: \"{error.Message}\"", error.Stack, error.InnerException);
Expand Down Expand Up @@ -155,7 +155,7 @@ internal async Task<EnvironmentDetails> GetBrowserEnvironment()
/// <param name="exceptionAction"></param>
internal async Task InitializeAsync(Func<ErrorEvent, Task> onUnhandledJsException,
Action<string, BreadcrumbType, string?, Dictionary<string, object>?, string?>? breadcrumbAction,
Func<Exception, UserDetails?, List<string>?, Dictionary<string, string>?, CancellationToken, Task>? exceptionAction)
Func<Exception, UserDetails?, List<string>?, Dictionary<string, object>?, CancellationToken, Task>? exceptionAction)
{
_breadcrumbAction = breadcrumbAction;
_exceptionAction = exceptionAction;
Expand Down
4 changes: 2 additions & 2 deletions src/Raygun.Samples.Blazor.Maui/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public static MauiApp CreateMauiApp()
builder.Services.AddMauiBlazorWebView();

#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif

return builder.Build();
Expand Down
18 changes: 18 additions & 0 deletions src/Raygun.Tests.Blazor/RaygunBlazorClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -131,6 +133,22 @@ public async Task RaygunBlazorClient_BasicException_ShouldSend()
// Check user details from FakeRaygunUserProvider
raygunMsg.Details.User.FullName.Should().Be("Manager User");
raygunMsg.Details.User.Email.Should().Be("[email protected]");

// Check Environment details
raygunMsg.Details.Environment.Architecture.Should().NotBeEmpty();
raygunMsg.Details.Environment.Cpu.Should().NotBeEmpty();
raygunMsg.Details.Environment.DeviceName.Should().NotBeEmpty();
raygunMsg.Details.Environment.OSVersion.Should().NotBeEmpty();
raygunMsg.Details.Environment.ProcessorCount.Should().NotBeNull();
raygunMsg.Details.Environment.UtcOffset.Should().NotBeNull();
raygunMsg.Details.Environment.TotalPhysicalMemory.Should().NotBeNull();
raygunMsg.Details.Environment.TotalVirtualMemory.Should().NotBeNull();

// Check Browser environment details from UserCustomData
var browserEnvJson = raygunMsg.Details.UserCustomData["BrowserEnvironment"];
browserEnvJson.Should().NotBeNull();
var browserEnv = JsonSerializer.Deserialize<EnvironmentDetails>((JsonElement)browserEnvJson, _jsonSerializerOptions)!;
browserEnv.BrowserName.Should().Be("Firefox");
}

/// <summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Raygun.Tests.Blazor/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ public void ApiDocsPayload_ShouldDeserialize()
result.Details.Tags[1].Should().Be("tag 2");
result.Details.Tags[2].Should().Be("tag-3");
result.Details.UserCustomData.Should().NotBeNull().And.HaveCount(2);
result.Details.UserCustomData["domain"].Should().Be("WORKPLACE");
result.Details.UserCustomData["area"].Should().Be("51");
// Cast to String to avoid issues with the dynamic object type.
result.Details.UserCustomData["domain"].ToString().Should().Be("WORKPLACE");
result.Details.UserCustomData["area"].ToString().Should().Be("51");
result.Details.Request.Should().NotBeNull();
result.Details.Request.HostName.Should().Be("https://raygun.io");
result.Details.Request.Url.Should().Be("/documentation/integrations/api");
Expand Down
Loading