Skip to content

Commit

Permalink
feat: #16 Include host machine environment details (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
miquelbeltran authored Oct 16, 2024
1 parent f4ea4fb commit 184834b
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 8 deletions.
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
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

0 comments on commit 184834b

Please sign in to comment.