Skip to content

Commit

Permalink
feat(templates): improve logging in Boilerplate #9251 (#9252)
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmoradi authored Nov 16, 2024
1 parent 36a3977 commit e955f00
Show file tree
Hide file tree
Showing 21 changed files with 172 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Using Include="System.Net.Http.Json" />
<Using Include="System.Collections.Concurrent" />
<Using Include="Microsoft.JSInterop" />
<Using Include="Microsoft.Extensions.Logging" />
<Using Include="Microsoft.AspNetCore.Components" />
<Using Include="Microsoft.AspNetCore.Authorization" />
<Using Include="Microsoft.AspNetCore.Components.Authorization" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//+:cnd:noEmit
using Microsoft.Extensions.Logging;
//#if (signalR == true)
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
Expand All @@ -8,6 +7,7 @@
//#if (appInsights == true)
using BlazorApplicationInsights.Interfaces;
//#endif
using Microsoft.AspNetCore.Components.Routing;

namespace Boilerplate.Client.Core.Components;

Expand All @@ -21,6 +21,7 @@ public partial class ClientAppCoordinator : AppComponentBase
//#if (signalR == true)
private HubConnection? hubConnection;
[AutoInject] private Notification notification = default!;
[AutoInject] private ILogger<HubConnection> signalRLogger = default!;
//#endif
//#if (notification == true)
[AutoInject] private IPushNotificationService pushNotificationService = default!;
Expand All @@ -31,18 +32,31 @@ public partial class ClientAppCoordinator : AppComponentBase
[AutoInject] private Navigator navigator = default!;
[AutoInject] private IJSRuntime jsRuntime = default!;
[AutoInject] private IStorageService storageService = default!;
[AutoInject] private ILogger<ClientAppCoordinator> logger = default!;
[AutoInject] private AuthenticationManager authManager = default!;
[AutoInject] private ILogger<Navigator> navigatorLogger = default!;
[AutoInject] private CultureInfoManager cultureInfoManager = default!;
[AutoInject] private ILogger<AuthenticationManager> authLogger = default!;
[AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!;

protected override async Task OnInitAsync()
{
AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged;
if (AppPlatform.IsBlazorHybrid)
{
if (CultureInfoManager.MultilingualEnabled)
{
cultureInfoManager.SetCurrentCulture(new Uri(NavigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture
await storageService.GetItem("Culture") ?? // 2- User settings
CultureInfo.CurrentUICulture.Name); // 3- OS settings
}

await SetupBodyClasses();
}

if (InPrerenderSession is false)
{
NavigationManager.LocationChanged += NavigationManager_LocationChanged;
AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged;

TelemetryContext.UserAgent = await navigator.GetUserAgent();
TelemetryContext.TimeZone = await jsRuntime.GetTimeZone();
TelemetryContext.Culture = CultureInfo.CurrentCulture.Name;
Expand All @@ -66,21 +80,14 @@ protected override async Task OnInitAsync()
AuthenticationStateChanged(AuthenticationManager.GetAuthenticationStateAsync());
}

if (AppPlatform.IsBlazorHybrid)
{
if (CultureInfoManager.MultilingualEnabled)
{
cultureInfoManager.SetCurrentCulture(new Uri(NavigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture
await storageService.GetItem("Culture") ?? // 2- User settings
CultureInfo.CurrentUICulture.Name); // 3- OS settings
}

await SetupBodyClasses();
}

await base.OnInitAsync();
}

private void NavigationManager_LocationChanged(object? sender, LocationChangedEventArgs e)
{
navigatorLogger.LogInformation("Navigation's location changed to {Location}", e.Location);
}

private async void AuthenticationStateChanged(Task<AuthenticationState> task)
{
try
Expand Down Expand Up @@ -166,7 +173,7 @@ private async Task ConnectSignalR()

hubConnection.On<string>(SignalREvents.PUBLISH_MESSAGE, async (message) =>
{
logger.LogInformation("Message {Message} received from server.", message);
signalRLogger.LogInformation("Message {Message} received from server.", message);
PubSubService.Publish(message);
});

Expand All @@ -190,7 +197,7 @@ private async Task ConnectSignalR()
private async Task HubConnectionConnected(string? connectionId)
{
PubSubService.Publish(ClientPubSubMessages.IS_ONLINE_CHANGED, true);
logger.LogInformation("SignalR connection {ConnectionId} established.", connectionId);
signalRLogger.LogInformation("SignalR connection {ConnectionId} established.", connectionId);
}

private async Task HubConnectionDisconnected(Exception? exception)
Expand All @@ -199,16 +206,20 @@ private async Task HubConnectionDisconnected(Exception? exception)

if (exception is null)
{
logger.LogInformation("SignalR connection lost."); // Was triggered intentionally by either server or client.
signalRLogger.LogInformation("SignalR connection lost."); // Was triggered intentionally by either server or client.
}
else
{
signalRLogger.LogWarning(exception, "SignalR connection lost.");

if (exception is HubException && exception.Message.EndsWith(nameof(AppStrings.UnauthorizedException)))
{
await AuthenticationManager.RefreshToken(CurrentCancellationToken);
try
{
await AuthenticationManager.RefreshToken(CurrentCancellationToken);
}
catch { }
}

logger.LogError(exception, "SignalR connection lost.");
}
}

Expand Down Expand Up @@ -244,6 +255,7 @@ private async Task SetupBodyClasses()

protected override async ValueTask DisposeAsync(bool disposing)
{
NavigationManager.LocationChanged -= NavigationManager_LocationChanged;
AuthenticationManager.AuthenticationStateChanged -= AuthenticationStateChanged;

//#if (signalR == true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@inherits AppComponentBase
@inherits AppComponentBase

<div>
<BitModal @bind-IsOpen="isOpen" FullSize Class="modal">
Expand Down Expand Up @@ -58,7 +58,7 @@
Variant="BitVariant.Text"
IconName="@BitIconName.Copy"
OnClick="() => CopyException(logIndex.item)" />
<BitText Style="white-space:nowrap" Color="GetColor(logIndex.item.Level)">@logIndex.item.Message</BitText>
<BitText Style="white-space:nowrap" Color="GetColor(logIndex.item.Level)">@($"{logIndex.item.Category}: {logIndex.item.Message}")</BitText>
</BitStack>
</RowTemplate>
</BitBasicList>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Boilerplate.Client.Core.Services.DiagnosticLog;
using Microsoft.Extensions.Logging;

namespace Boilerplate.Client.Core.Components.Layout;

Expand Down Expand Up @@ -59,7 +58,7 @@ private void HandleOnSortClick()

private void FilterLogs()
{
filteredLogs = allLogs.WhereIf(string.IsNullOrEmpty(searchText) is false, l => l.Message?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true)
filteredLogs = allLogs.WhereIf(string.IsNullOrEmpty(searchText) is false, l => l.Message?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true || l.Category?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true)
.Where(l => filterLogLevels.Contains(l.Level));
if (isDescendingSort)
{
Expand All @@ -80,7 +79,7 @@ private async Task CopyException(DiagnosticLog log)
{
var stateToCopy = string.Join(Environment.NewLine, log.State?.Select(i => $"{i.Key}: {i.Value}") ?? []);

await clipboard.WriteText($"{log.Message}{Environment.NewLine}{log.Exception?.ToString()}{Environment.NewLine}{stateToCopy}");
await clipboard.WriteText($"{log.Category}{Environment.NewLine}{log.Message}{Environment.NewLine}{log.Exception?.ToString()}{Environment.NewLine}{stateToCopy}");
}

private async Task GoTop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ await WrapRequest(async () =>
{
var signInResponse = await identityController.ConfirmEmail(new() { Email = emailModel.Email, Token = emailModel.Token }, CurrentCancellationToken);

await AuthenticationManager.OnNewToken(signInResponse, true);
await AuthenticationManager.StoreTokens(signInResponse, true);

NavigationManager.NavigateTo(Urls.HomePage, replace: true);

Expand All @@ -103,7 +103,7 @@ await WrapRequest(async () =>
{
var signInResponse = await identityController.ConfirmPhone(new() { PhoneNumber = phoneModel.PhoneNumber, Token = phoneModel.Token }, CurrentCancellationToken);

await AuthenticationManager.OnNewToken(signInResponse, true);
await AuthenticationManager.StoreTokens(signInResponse, true);

NavigationManager.NavigateTo(Urls.HomePage, replace: true);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging;

namespace Boilerplate.Client.Core.Components.Pages;
namespace Boilerplate.Client.Core.Components.Pages;

public partial class NotAuthorizedPage
{
Expand All @@ -9,8 +7,6 @@ public partial class NotAuthorizedPage

[SupplyParameterFromQuery(Name = "return-url"), Parameter] public string? ReturnUrl { get; set; }

[AutoInject] private ILogger<NotAuthorizedPage> logger = default!;

protected override async Task OnParamsSetAsync()
{
user = (await AuthenticationStateTask).User;
Expand Down Expand Up @@ -43,8 +39,6 @@ protected override async Task OnAfterFirstRenderAsync()
isRefreshingToken = false;
}

logger.LogInformation("Refreshing access token.");

if (ReturnUrl is not null)
{
var @char = ReturnUrl.Contains('?') ? '&' : '?'; // The RedirectUrl may already include a query string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle
// handlers, such as ASP.NET Core's `HttpMessageHandler` from the Test Host, which is useful for integration tests.
services.AddScoped<HttpMessageHandlersChainFactory>(serviceProvider => transportHandler =>
{
var constructedHttpMessageHandler = ActivatorUtilities.CreateInstance<RequestHeadersDelegationHandler>(serviceProvider,
var constructedHttpMessageHandler = ActivatorUtilities.CreateInstance<LoggingDelegatingHandler>(serviceProvider,
[ActivatorUtilities.CreateInstance<RequestHeadersDelegationHandler>(serviceProvider,
[ActivatorUtilities.CreateInstance<AuthDelegatingHandler>(serviceProvider,
[ActivatorUtilities.CreateInstance<RetryDelegatingHandler>(serviceProvider,
[ActivatorUtilities.CreateInstance<ExceptionDelegatingHandler>(serviceProvider, [transportHandler])])])]);
[ActivatorUtilities.CreateInstance<ExceptionDelegatingHandler>(serviceProvider, [transportHandler])])])])]);
return constructedHttpMessageHandler;
});
services.AddScoped<AuthDelegatingHandler>();
services.AddScoped<RetryDelegatingHandler>();
services.AddScoped<LoggingDelegatingHandler>();
services.AddScoped<ExceptionDelegatingHandler>();
services.AddScoped<RequestHeadersDelegationHandler>();
services.AddScoped(serviceProvider =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public partial class AuthenticationManager : AuthenticationStateProvider
[AutoInject] private IExceptionHandler exceptionHandler = default!;
[AutoInject] private IStringLocalizer<AppStrings> localizer = default!;
[AutoInject] private IIdentityController identityController = default!;
[AutoInject] private ILogger<AuthenticationManager> authLogger = default!;

/// <summary>
/// Sign in and return whether the user requires two-factor authentication.
Expand All @@ -30,38 +31,51 @@ public async Task<bool> SignIn(SignInRequestDto request, CancellationToken cance

if (response.RequiresTwoFactor) return true;

await OnNewToken(response!, request.RememberMe);
await StoreTokens(response!, request.RememberMe);

return false;
}

public async Task OnNewToken(TokenResponseDto response, bool? rememberMe = null)
public async Task StoreTokens(TokenResponseDto response, bool? rememberMe = null)
{
await StoreTokens(response, rememberMe);
if (rememberMe is null)
{
rememberMe = await storageService.IsPersistent("refresh_token");
}

var state = await GetAuthenticationStateAsync();
await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true);
await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true);

if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized())
{
await cookie.Set(new()
{
Name = "access_token",
Value = response.AccessToken,
MaxAge = rememberMe is true ? response.ExpiresIn : null, // to create a session cookie
Path = "/",
SameSite = SameSite.Strict,
Secure = AppEnvironment.IsDev() is false
});
}

NotifyAuthenticationStateChanged(Task.FromResult(state));
NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync()));
}

public async Task SignOut(CancellationToken cancellationToken)
{
try
{
if (string.IsNullOrEmpty(await storageService.GetItem("access_token")) is false)
{
await userController.SignOut(cancellationToken);
}
await userController.SignOut(cancellationToken);
}
catch (Exception exp) when (exp is ServerConnectionException or UnauthorizedException)
{
// The user might sign out while the app is offline, making token refresh attempts fail.
// These exceptions are intentionally ignored in this case.
}
finally
{
await storageService.RemoveItem("access_token");
await storageService.RemoveItem("refresh_token");
if (AppPlatform.IsBlazorHybrid is false)
{
await cookie.Remove("access_token");
}
NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync()));
await ClearTokens();
}
}

Expand All @@ -70,31 +84,25 @@ public async Task<string> RefreshToken(CancellationToken cancellationToken)
try
{
await semaphore.WaitAsync();

authLogger.LogInformation("Refreshing access token");
try
{
string? refresh_token = await storageService.GetItem("refresh_token");
if (string.IsNullOrEmpty(refresh_token))
throw new UnauthorizedException(localizer[nameof(AppStrings.YouNeedToSignIn)]);

var refreshTokenResponse = await identityController.Refresh(new() { RefreshToken = refresh_token }, cancellationToken);
await StoreTokens(refreshTokenResponse!);
return refreshTokenResponse.AccessToken;
await StoreTokens(refreshTokenResponse);
return refreshTokenResponse.AccessToken!;
}
catch (UnauthorizedException) // refresh_token is either invalid or expired.
{
if (AppPlatform.IsBlazorHybrid is false)
{
await cookie.Remove("access_token");
}
await storageService.RemoveItem("access_token");
await storageService.RemoveItem("refresh_token");
await ClearTokens();
throw;
}
}
finally
{
NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync()));
semaphore.Release();
}
}
Expand Down Expand Up @@ -123,27 +131,14 @@ public override async Task<AuthenticationState> GetAuthenticationStateAsync()
}
}

private async Task StoreTokens(TokenResponseDto response, bool? rememberMe = null)
private async Task ClearTokens()
{
if (rememberMe is null)
{
rememberMe = await storageService.IsPersistent("refresh_token");
}

await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true);
await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true);

if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized())
await storageService.RemoveItem("access_token");
await storageService.RemoveItem("refresh_token");
if (AppPlatform.IsBlazorHybrid is false)
{
await cookie.Set(new()
{
Name = "access_token",
Value = response.AccessToken,
MaxAge = rememberMe is true ? response.ExpiresIn : null, // to create a session cookie
Path = "/",
SameSite = SameSite.Strict,
Secure = AppEnvironment.IsDev() is false
});
await cookie.Remove("access_token");
}
NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync()));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//+:cnd:noEmit
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;

namespace Boilerplate.Client.Core.Services;
Expand Down
Loading

0 comments on commit e955f00

Please sign in to comment.