Skip to content

Commit

Permalink
feat: #27 Internal RaygunLogger (#28)
Browse files Browse the repository at this point in the history
* add ApiKey to appsettings.json in tests

* apply naming suggestions by Rider IDE

* inject mocked IJSRuntime

* HttpMethod should be String not type HttpMethod

* wip fixing tests

* simple fix

* fix tests

* cleanup

* cleanup

* test cleanup

* more cleanup

* restore file

* ci: dotnet formatting

* simplify job

* test break formatting

* fix format

* dotnet format

* fixing warnings

* fix warnings

* 5 warnings

* ci: Code formatting (#7)

* ci: dotnet formatting

* simplify job

* test break formatting

* fix format

* dotnet format

* fix acronym formatting

* dotnet format

* refactor: remove .NetCore from Raygun.NetCore.Blazor

* WIP

* fix visibility after refactor

* more refactor

* fix merge errors

* docs: update README.md

* feat: Internal RaygunLogger

* cleanup

* cleanup post-merge

* cleanup logger implementation

* add logger to readme

* Improve RaygunLogger implementation

* optimization
  • Loading branch information
miquelbeltran authored Aug 2, 2024
1 parent 37d4a26 commit 7ba47e8
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 12 deletions.
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,28 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.UseRaygunBlazor();
```

The `RaygunSettings` will be obtained from the `appsettings.json` under the configuration section name `Raygun`.
#### Raygun Settings

Raygun for Blazor uses the `appsettings.json` to load configuration settings.

`RaygunSettings` will be obtained from the configuration section name `Raygun`.

For example:

```json
{
"Raygun": {
"ApiKey": "YOUR_API_KEY",
"CatchUnhandledExceptions": "false",
"LogLevel": "Debug"
}
}
```

For all configuration values, check the `RaygunSettings` class under `src/Raygun.Blazor/RaygunSettings.cs`.

See [Configuration in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0) to learn more about managing application settings.

#### Initalize the client

Inject the `RaygunBlazorClient` in your code:
Expand Down Expand Up @@ -56,6 +74,26 @@ Call to `raygunClient.RecordBreadcrumb(...);`

Currently used in `src/Raygun.Samples.Blazor.WebAssembly/App.razor`

### Internal logger

Raygun for Blazor uses an internal logger to help facilitate the integration of the package.
The default log level is `"Warning"`.
To completely disable the internal logger, set the `"LogLevel"` setting to `"None"`.

For example:

```json
{
"Raygun": {
"LogLevel": "None"
}
}
```

For all configuration values, check the `RaygunLogLevel` enum under `src/Raygun.Blazor/Logging/RaygunLogLevel.cs`.

---

## Example Project

Example project is located in `src/Raygun.Samples.Blazor.WebAssembly`
Expand All @@ -77,6 +115,8 @@ To run the example:

A browser window to `http://localhost:5010/` should automatically open.

---

## Publishing

- [ ] TODO: Packagre publishing instructions, e.g. NuGet publish instructions
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
using System;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Raygun.Blazor.Extensions;
using Raygun.Blazor.Logging;


namespace Raygun.Blazor.WebAssembly.Extensions
{

/// <summary>
/// Extensions for registering RaygunBlazorClient and related services with a Blazor WebAssembly application.
/// </summary>
public static class Raygun_WebAssembly_WebAssemblyHostBuilderExtensions
{

/// <summary>
/// Configures the RaygunBlazorClient and related services for use in a Blazor WebAssembly application.
/// </summary>
Expand All @@ -41,14 +40,12 @@ public static void UseRaygunBlazor(this WebAssemblyHostBuilder builder, string c
var raygunSettings = sp.GetRequiredService<IOptions<RaygunSettings>>().Value;
client.BaseAddress = new Uri(raygunSettings.Endpoint);
client.DefaultRequestHeaders.Add("X-ApiKey", raygunSettings.ApiKey);
client.DefaultRequestHeaders.CacheControl = Raygun_Blazor_IServiceCollectionExtensions.CacheControlHeaderValue;
client.DefaultRequestHeaders.CacheControl =
Raygun_Blazor_IServiceCollectionExtensions.CacheControlHeaderValue;
// TODO: RWM: Set user agent
});

builder.Services.AddSingleton<RaygunBlazorClient>();

}

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Raygun.Blazor.Logging;

namespace Raygun.Blazor.Extensions
{
Expand Down
34 changes: 34 additions & 0 deletions src/Raygun.Blazor/Logging/IRaygunLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Raygun.Blazor.Logging
{
/// <summary>
/// Raygun logger interface.
/// </summary>
internal interface IRaygunLogger
{
/// <summary>
/// Prints an error message with level Error.
/// </summary>
/// <param name="message"></param>
void Error(string message);
/// <summary>
/// Prints a warning message with level Warning.
/// </summary>
/// <param name="message"></param>
void Warning(string message);
/// <summary>
/// Prints an information message with level Info.
/// </summary>
/// <param name="message"></param>
void Info(string message);
/// <summary>
/// Prints a debug message with level Debug.
/// </summary>
/// <param name="message"></param>
void Debug(string message);
/// <summary>
/// Prints a verbose message with level Verbose.
/// </summary>
/// <param name="message"></param>
void Verbose(string message);
}
}
33 changes: 33 additions & 0 deletions src/Raygun.Blazor/Logging/RaygunLogLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Raygun.Blazor.Logging
{
/// <summary>
/// Hierarchy level for internal logs
/// </summary>
public enum RaygunLogLevel
{
/// <summary>
/// Use to disable console logs
/// </summary>
None = 0,
/// <summary>
/// Highest level of error
/// </summary>
Error,
/// <summary>
/// Warning level of error
/// </summary>
Warning,
/// <summary>
/// Information level of error
/// </summary>
Info,
/// <summary>
/// Debugging level of error
/// </summary>
Debug,
/// <summary>
/// Lowest level of error
/// </summary>
Verbose
}
}
83 changes: 83 additions & 0 deletions src/Raygun.Blazor/Logging/RaygunLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;

namespace Raygun.Blazor.Logging
{
/// <summary>
/// Implementation of the Raygun logger.
/// </summary>
internal class RaygunLogger : IRaygunLogger
{
private readonly RaygunLogLevel _logLevel;
private static RaygunLogger? _raygunLogger;

/// <summary>
/// Create or retrieve instance of IRaygunLogger.
/// Returns null if the logLevel is None.
/// </summary>
internal static IRaygunLogger? Create(RaygunLogLevel logLevel)
{
_raygunLogger = logLevel == RaygunLogLevel.None ? null : _raygunLogger ?? new RaygunLogger(logLevel);
return _raygunLogger;
}

private RaygunLogger(RaygunLogLevel logLevel)
{
_logLevel = logLevel;
Warning("[RaygunLogger] Internal logger initialized with log level: " + _logLevel);
Warning("[RaygunLogger] Disable internal logger by setting LogLevel to None in Raygun settings");
}

private const string RaygunPrefix = "Raygun:";

/// <inheritdoc />
public void Error(string message)
{
Log(RaygunLogLevel.Error, message);
}

/// <inheritdoc />
public void Warning(string message)
{
Log(RaygunLogLevel.Warning, message);
}

/// <inheritdoc />
public void Info(string message)
{
Log(RaygunLogLevel.Info, message);
}

/// <inheritdoc />
public void Debug(string message)
{
Log(RaygunLogLevel.Debug, message);
}

/// <inheritdoc />
public void Verbose(string message)
{
Log(RaygunLogLevel.Verbose, message);
}

private void Log(RaygunLogLevel level, string message)
{
if (_logLevel == RaygunLogLevel.None)
{
return;
}

if (level > _logLevel) return;

try
{
// Only log the first letter of the log level e.g. "I" for Info
var initial = level.ToString()[0..1].ToUpper();
Console.WriteLine($"{RaygunPrefix} [{initial}] {message}");
}
catch
{
// ignored
}
}
}
}
28 changes: 26 additions & 2 deletions src/Raygun.Blazor/RaygunBlazorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using KristofferStrube.Blazor.WebIDL.Exceptions;
using KristofferStrube.Blazor.Window;
using Microsoft.Extensions.Options;
using Raygun.Blazor.Logging;
using Raygun.Blazor.Models;

namespace Raygun.Blazor
Expand All @@ -31,6 +32,7 @@ public class RaygunBlazorClient
// private readonly IRaygunQueueManager? _queueManager;
private readonly RaygunSettings _raygunSettings;
// private readonly IRaygunUserManager? _userManager;
private readonly IRaygunLogger? _raygunLogger;

#endregion

Expand All @@ -45,12 +47,14 @@ public class RaygunBlazorClient
/// <remarks>
/// You should not usually create a new instance yourself, instead get a usable instance from the DI container by injecting it into the Blazor page directly.
/// </remarks>
public RaygunBlazorClient(IOptions<RaygunSettings> raygunSettings, IHttpClientFactory httpClientFactory, RaygunBrowserInterop browserInterop/*, IRaygunQueueManager queueManager, IRaygunUserManager userManager, IRaygunOfflineStore offlineStore*/)
public RaygunBlazorClient(IOptions<RaygunSettings> raygunSettings, IHttpClientFactory httpClientFactory, RaygunBrowserInterop browserInterop /*, IRaygunQueueManager queueManager, IRaygunUserManager userManager, IRaygunOfflineStore offlineStore*/)
{
_raygunLogger = RaygunLogger.Create(raygunSettings.Value.LogLevel);
// RWM: We do this first because there is no point consuming CPU cycles setting properties if the API key is missing.
_raygunSettings = raygunSettings.Value;
if (string.IsNullOrWhiteSpace(_raygunSettings.ApiKey))
{
_raygunLogger?.Error("[RaygunBlazorClient] A Raygun API Key was not provided. Please check your settings and try again.");
// ReSharper disable once NotResolvedInText
throw new ArgumentNullException("RaygunSettings.ApiKey", "A Raygun API Key was not provided. Please check your settings and try again.");
}
Expand All @@ -63,6 +67,7 @@ public RaygunBlazorClient(IOptions<RaygunSettings> raygunSettings, IHttpClientFa
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
_raygunLogger?.Debug("[RaygunBlazorClient] Initialized.");
}

#endregion
Expand Down Expand Up @@ -114,6 +119,7 @@ public async Task InitializeAsync()
if (_browserInterop.RaygunScriptReference is null)
{
await _browserInterop.InitializeAsync(OnUnhandledJsException, RecordBreadcrumb, RecordExceptionAsync);
_raygunLogger?.Debug("[RaygunBlazorClient] JavaScript Interop initialized.");
}
}

Expand All @@ -134,6 +140,7 @@ public void RecordBreadcrumb(string? message, BreadcrumbType breadcrumbType = Br
Dictionary<string, object>? customData = null, string? platform = "DotNet")
{
_breadcrumbs.Add(new BreadcrumbDetails(message, breadcrumbType, category, customData, platform));
_raygunLogger?.Verbose("[RaygunBlazorClient] Breadcrumb recorded: " + message);
}


Expand All @@ -152,6 +159,7 @@ public void RecordBreadcrumb(string? message, BreadcrumbType breadcrumbType = Br
public async Task RecordExceptionAsync(Exception ex, List<string>? tags = null, bool addUserDetails = false, Dictionary<string, string>? userCustomData = null,
CancellationToken cancellationToken = default)
{
_raygunLogger?.Verbose("[RaygunBlazorClient] Recording exception: " + ex);
await InitializeAsync();

var appVersion = _raygunSettings.ApplicationVersion ??
Expand All @@ -174,6 +182,8 @@ public async Task RecordExceptionAsync(Exception ex, List<string>? tags = null,
};
_breadcrumbs.Clear();

_raygunLogger?.Debug("[RaygunBlazorClient] Sending request to Raygun: " + request);

// TODO: RWM: Queue the request to be sent out-of-band.
//queueManager.Enqueue(request);

Expand All @@ -187,6 +197,15 @@ public async Task RecordExceptionAsync(Exception ex, List<string>? tags = null,
// 403 Invalid API Key - The value specified in the header X-ApiKey did not match with a user.
// 413 Request entity too large - The maximum size of a JSON payload is 128KB.
// 429 Too Many Requests - Plan limit exceeded for month or plan expired

if (!response.IsSuccessStatusCode)
{
_raygunLogger?.Error("[RaygunBlazorClient] Failed to send request to Raygun: " + response.StatusCode);
}
else
{
_raygunLogger?.Debug("[RaygunBlazorClient] Request sent to Raygun: " + response.StatusCode);
}
}


Expand Down Expand Up @@ -214,8 +233,13 @@ public async Task FlushAsync()
/// </remarks>
internal async Task OnUnhandledJsException(ErrorEvent errorEvent)
{
_raygunLogger?.Verbose("[RaygunBlazorClient] Unhandled JavaScript exception caught: " + errorEvent);
WebIDLException? exception = await errorEvent.GetErrorAsExceptionAsync();
if (exception is null) return;
if (exception is null)
{
_raygunLogger?.Warning("[RaygunBlazorClient] Failed to convert JavaScript error to WebIDLException.");
return;
}
await RecordExceptionAsync(exception, ["UnhandledException", "Blazor", "JavaScript"]);
}

Expand Down
Loading

0 comments on commit 7ba47e8

Please sign in to comment.