Skip to content

Commit

Permalink
Merge pull request #409 from NielsPilgaard/feature/metrics
Browse files Browse the repository at this point in the history
Feature/metrics
  • Loading branch information
NielsPilgaard authored Aug 10, 2024
2 parents cc01b4d + 7c3a0a2 commit 55f8908
Show file tree
Hide file tree
Showing 17 changed files with 198 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ public static IServiceCollection AddDataForsyningenClient(this IServiceCollectio

var pingCircuitBreakerOptions = new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
BreakDuration = TimeSpan.FromMinutes(5),
MinimumThroughput = 2,
FailureRatio = 0.25,
BreakDuration = TimeSpan.FromMinutes(1),
ShouldHandle = arguments =>
new ValueTask<bool>(
arguments.Outcome.Exception is not null ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Extensions.Primitives;
using System.Security.Claims;
using System.Text.Json;
using Jordnaer.Features.Metrics;

namespace Jordnaer.Components.Account;

Expand Down Expand Up @@ -46,6 +47,9 @@ public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEn
[FromForm] string returnUrl) =>
{
await signInManager.SignOutAsync();

JordnaerMetrics.LogoutCounter.Add(1);

return TypedResults.LocalRedirect(string.IsNullOrEmpty(returnUrl)
? "~/"
: returnUrl);
Expand Down
19 changes: 5 additions & 14 deletions src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
RedirectManager.RedirectToWithStatus("Account/Login", "Fejl - Det lykkedes ikke at finde dit login. Prøv venligst igen.", HttpContext);
}

JordnaerMetrics.ExternalLoginCounter.Add(1);

var accessToken = _externalLoginInfo.AuthenticationProperties?.GetTokenValue("access_token");
if (accessToken is not null)
{
Expand Down Expand Up @@ -146,7 +148,7 @@
private async Task OnValidSubmitAsync()
{
var emailStore = GetEmailStore();
var user = CreateUser();
var user = new ApplicationUser();

await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
Expand Down Expand Up @@ -182,24 +184,13 @@
RedirectManager.RedirectTo("Account/RegisterConfirmation", queryParameters: new() { ["email"] = Input.Email });
}

JordnaerMetrics.ExternalLoginCounter.Add(1);

await SignInManager.SignInAsync(user, isPersistent: false, _externalLoginInfo.LoginProvider);
RedirectManager.RedirectTo(FirstLoginReturnUrl);
}
}

private static ApplicationUser CreateUser()
{
try
{
return Activator.CreateInstance<ApplicationUser>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
$"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor");
}
}

private IUserEmailStore<ApplicationUser> GetEmailStore()
{
if (!UserManager.SupportsUserEmail)
Expand Down
2 changes: 2 additions & 0 deletions src/web/Jordnaer/Components/Account/Pages/Login.razor
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
lockoutOnFailure: true);
if (result.Succeeded)
{
JordnaerMetrics.LoginCounter.Add(1);

Logger.LogInformation("User logged in.");

RedirectManager.RedirectTo(FirstLoginReturnUrl);
Expand Down
6 changes: 5 additions & 1 deletion src/web/Jordnaer/Consumers/SendEmailConsumer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Jordnaer.Extensions;
using Jordnaer.Features.Email;
using Jordnaer.Features.Metrics;
using MassTransit;
using Polly;
using Polly.CircuitBreaker;
Expand All @@ -15,7 +16,8 @@ public class SendEmailConsumer(
ISendGridClient sendGridClient)
: IConsumer<SendEmail>
{
private static readonly ResiliencePipeline<Response> Retry = new ResiliencePipelineBuilder<Response>()
private static readonly ResiliencePipeline<Response> Retry =
new ResiliencePipelineBuilder<Response>()
.AddRetry(new RetryStrategyOptions<Response>
{
ShouldHandle = new PredicateBuilder<Response>()
Expand Down Expand Up @@ -91,6 +93,8 @@ public async Task Consume(ConsumeContext<SendEmail> consumeContext)
{
logger.LogInformation("Email sent to {@Recipient}. Subject: {Subject}",
message.To?.Select(x => x.Email), message.Subject);

JordnaerMetrics.EmailsSentCounter.Add(1);
}
}
}
36 changes: 16 additions & 20 deletions src/web/Jordnaer/Consumers/SendMessageConsumer.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
using Jordnaer.Database;
using Jordnaer.Extensions;
using Jordnaer.Features.Chat;
using Jordnaer.Features.Metrics;
using Jordnaer.Shared;
using MassTransit;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

namespace Jordnaer.Consumers;

public class SendMessageConsumer : IConsumer<SendMessage>
public class SendMessageConsumer(
JordnaerDbContext context,
ILogger<SendMessageConsumer> logger,
IHubContext<ChatHub, IChatHub> chatHub)
: IConsumer<SendMessage>
{
private readonly JordnaerDbContext _context;
private readonly ILogger<SendMessageConsumer> _logger;
private readonly IHubContext<ChatHub, IChatHub> _chatHub;

public SendMessageConsumer(JordnaerDbContext context, ILogger<SendMessageConsumer> logger, IHubContext<ChatHub, IChatHub> chatHub)
{
_context = context;
_logger = logger;
_chatHub = chatHub;
}

public async Task Consume(ConsumeContext<SendMessage> consumeContext)
{
_logger.LogDebug("Consuming SendMessage message. ChatId: {ChatId}", consumeContext.Message.ChatId);
logger.LogDebug("Consuming SendMessage message. ChatId: {ChatId}", consumeContext.Message.ChatId);

var chatMessage = consumeContext.Message;

_context.ChatMessages.Add(
context.ChatMessages.Add(
new ChatMessage
{
ChatId = chatMessage.ChatId,
Expand All @@ -38,14 +32,14 @@ public async Task Consume(ConsumeContext<SendMessage> consumeContext)
SentUtc = chatMessage.SentUtc
});

var recipientIds = await _context.UserChats
var recipientIds = await context.UserChats
.Where(userChat => userChat.ChatId == chatMessage.ChatId)
.Select(userChat => userChat.UserProfileId)
.ToListAsync(consumeContext.CancellationToken);

foreach (var recipientId in recipientIds.Where(recipientId => recipientId != chatMessage.SenderId))
{
_context.UnreadMessages.Add(new UnreadMessage
context.UnreadMessages.Add(new UnreadMessage
{
RecipientId = recipientId,
ChatId = chatMessage.ChatId,
Expand All @@ -55,20 +49,22 @@ public async Task Consume(ConsumeContext<SendMessage> consumeContext)

try
{
await _context.SaveChangesAsync(consumeContext.CancellationToken);
await context.SaveChangesAsync(consumeContext.CancellationToken);

await _chatHub.Clients.Users(recipientIds).ReceiveChatMessage(chatMessage);
await chatHub.Clients.Users(recipientIds).ReceiveChatMessage(chatMessage);

await _context.Chats
await context.Chats
.Where(chat => chat.Id == chatMessage.ChatId)
.ExecuteUpdateAsync(call =>
call.SetProperty(chat => chat.LastMessageSentUtc, DateTime.UtcNow),
consumeContext.CancellationToken);
}
catch (Exception exception)
{
_logger.LogException(exception);
logger.LogException(exception);
throw;
}

JordnaerMetrics.ChatMessagesSentCounter.Add(1);
}
}
3 changes: 3 additions & 0 deletions src/web/Jordnaer/Consumers/StartChatConsumer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Jordnaer.Database;
using Jordnaer.Features.Chat;
using Jordnaer.Features.Metrics;
using Jordnaer.Shared;
using MassTransit;
using Microsoft.AspNetCore.SignalR;
Expand Down Expand Up @@ -76,5 +77,7 @@ public async Task Consume(ConsumeContext<StartChat> consumeContext)
}

await _chatNotificationService.NotifyRecipients(chat);

JordnaerMetrics.ChatStartedSentCounter.Add(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ public static WebApplicationBuilder AddOpenTelemetry(this WebApplicationBuilder
.AddProcessInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter(InstrumentationOptions.MeterName)
.AddMeter("Polly"))
.AddMeter("Polly")
.AddMeter("Jordnaer"))
.WithTracing(tracing =>
{
if (builder.Environment.IsDevelopment())
Expand All @@ -102,7 +103,8 @@ public static WebApplicationBuilder AddOpenTelemetry(this WebApplicationBuilder

tracing.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource(DiagnosticHeaders.DefaultListenerName);
.AddSource(DiagnosticHeaders.DefaultListenerName)
.AddSource("Jordnaer");
});

// Use the Aspire Dashboard in development
Expand Down
5 changes: 5 additions & 0 deletions src/web/Jordnaer/Features/Ad/SponsorAd.razor
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@

[Parameter]
public bool Fluid { get; set; } = true;

protected override void OnInitialized()
{
JordnaerMetrics.SponsorAdViewCounter.Add(1, new KeyValuePair<string, object?>("link", Link));
}
}
14 changes: 13 additions & 1 deletion src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Jordnaer.Database;
using Jordnaer.Features.Metrics;
using Jordnaer.Features.Search;
using Jordnaer.Shared;
using Microsoft.EntityFrameworkCore;
Expand All @@ -15,10 +16,11 @@ public class GroupSearchService(
IZipCodeService zipCodeService)
: IGroupSearchService
{

public async Task<GroupSearchResult> GetGroupsAsync(GroupSearchFilter filter,
CancellationToken cancellationToken = default)
{
JordnaerMetrics.GroupSearchesCounter.Add(1, MakeTagList(filter));

var groups = context.Groups
.AsNoTracking()
.AsQueryable()
Expand Down Expand Up @@ -83,4 +85,14 @@ public async Task<GroupSearchResult> GetGroupsAsync(GroupSearchFilter filter,

return (groups, true);
}

private static ReadOnlySpan<KeyValuePair<string, object?>> MakeTagList(GroupSearchFilter filter)
{
return new KeyValuePair<string, object?>[]
{
new(nameof(filter.Location), filter.Location),
new(nameof(filter.Categories), string.Join(',', filter.Categories ?? [])),
new(nameof(filter.WithinRadiusKilometers), filter.WithinRadiusKilometers)
};
}
}
3 changes: 3 additions & 0 deletions src/web/Jordnaer/Features/Groups/GroupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Serilog;
using System.Linq.Expressions;
using Jordnaer.Features.Authentication;
using Jordnaer.Features.Metrics;
using NotFound = OneOf.Types.NotFound;

namespace Jordnaer.Features.Groups;
Expand Down Expand Up @@ -280,6 +281,8 @@ not MembershipStatus.Rejected

public async Task<OneOf<Success, Error<string>>> CreateGroupAsync(Group group, CancellationToken cancellationToken = default)
{
JordnaerMetrics.GroupsCreatedCounter.Add(1);

logger.LogFunctionBegan();

if (currentUser.Id is null)
Expand Down
45 changes: 45 additions & 0 deletions src/web/Jordnaer/Features/Metrics/JordnaerMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Diagnostics.Metrics;
using System.Reflection;

namespace Jordnaer.Features.Metrics;

internal static class JordnaerMetrics
{
private static readonly AssemblyName AssemblyName = typeof(Program).Assembly.GetName();
internal static readonly Meter Meter =
new(name: AssemblyName.Name!,
version: AssemblyName.Version?.ToString());

internal static readonly Counter<int> ChatMessagesReceivedCounter =
Meter.CreateCounter<int>("jordnaer_chat_messages_received_total");
internal static readonly Counter<int> ChatMessagesSentCounter =
Meter.CreateCounter<int>("jordnaer_chat_messages_sent_total");
internal static readonly Counter<int> ChatStartedSentCounter =
Meter.CreateCounter<int>("jordnaer_chat_chat_started_sent_total");
internal static readonly Counter<int> ChatStartedReceivedCounter =
Meter.CreateCounter<int>("jordnaer_chat_chat_started_received_total");

internal static readonly Counter<int> EmailsSentCounter =
Meter.CreateCounter<int>("jordnaer_email_emails_sent_total");

internal static readonly Counter<int> LoginCounter =
Meter.CreateCounter<int>("jordnaer_auth_login_total");
internal static readonly Counter<int> ExternalLoginCounter =
Meter.CreateCounter<int>("jordnaer_auth_external_login_total");
internal static readonly Counter<int> FirstLoginCounter =
Meter.CreateCounter<int>("jordnaer_auth_first_login_total");
internal static readonly Counter<int> LogoutCounter =
Meter.CreateCounter<int>("jordnaer_auth_logout_total");

internal static readonly Counter<int> GroupsCreatedCounter =
Meter.CreateCounter<int>("jordnaer_group_groups_created_total");

internal static readonly Counter<int> GroupSearchesCounter =
Meter.CreateCounter<int>("jordnaer_group_group_searches_total");

internal static readonly Counter<int> UserSearchesCounter =
Meter.CreateCounter<int>("jordnaer_user_user_searches_total");

internal static readonly Counter<int> SponsorAdViewCounter =
Meter.CreateCounter<int>("jordnaer_ad_sponsor_views_total");
}
9 changes: 6 additions & 3 deletions src/web/Jordnaer/Features/Theme/JordnaerPalette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ public static class JordnaerPalette
/// <summary>
/// Dark Blue. Used for body text
/// </summary>
public static readonly MudColor BlueBody = "#41556b";
public static readonly MudColor BlueBody = "#41556b";

public static readonly string BlueBodyTextStyle = $"color: {BlueBody}";

/// <summary>
/// Dark Red. Used for small texts, payoffs, quotes
/// </summary>
public static readonly MudColor RedHeader = "#673417";

public static readonly MudColor RedHeader = "#673417";

public static readonly string RedHeaderTextStyle = $"color: {RedHeader}";
/// <summary>
/// Beige. Used as background for text where <see cref="YellowBackground"/> and <see cref="GreenBackground"/> are too dark/saturated.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/web/Jordnaer/Features/UserSearch/UserSearchService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Jordnaer.Database;
using Jordnaer.Features.Metrics;
using Jordnaer.Features.Search;
using Jordnaer.Shared;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -40,6 +41,8 @@ public async Task<List<UserSlim>> GetUsersByNameAsync(string currentUserId, stri

public async Task<UserSearchResult> GetUsersAsync(UserSearchFilter filter, CancellationToken cancellationToken = default)
{
JordnaerMetrics.UserSearchesCounter.Add(1, MakeTagList(filter));

var users = context.UserProfiles
.Where(user => !string.IsNullOrEmpty(user.UserName))
.AsQueryable();
Expand Down Expand Up @@ -170,4 +173,14 @@ private static IQueryable<UserProfile> ApplyChildFilters(UserSearchFilter filter

return users;
}

private static ReadOnlySpan<KeyValuePair<string, object?>> MakeTagList(UserSearchFilter filter)
{
return new KeyValuePair<string, object?>[]
{
new(nameof(filter.Location), filter.Location),
new(nameof(filter.Categories), string.Join(',', filter.Categories ?? [])),
new(nameof(filter.WithinRadiusKilometers), filter.WithinRadiusKilometers)
};
}
}
Loading

0 comments on commit 55f8908

Please sign in to comment.