feat: add preprod observability foundation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.ContentItems.Data;
|
||||
using Socialize.Api.Modules.ContentItems.Contracts;
|
||||
@@ -37,7 +38,8 @@ internal class SubmitApprovalDecisionHandler(
|
||||
AccessScopeService accessScopeService,
|
||||
ApprovalWorkflowRuntimeService approvalWorkflowRuntimeService,
|
||||
IContentItemActivityWriter activityWriter,
|
||||
INotificationEventWriter notificationEventWriter)
|
||||
INotificationEventWriter notificationEventWriter,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<SubmitApprovalDecisionRequest, ApprovalRequestDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -157,6 +159,7 @@ internal class SubmitApprovalDecisionHandler(
|
||||
$$"""{"stage":"{{approval.Stage}}","status":"{{contentItem.Status}}"}"""),
|
||||
ct);
|
||||
}
|
||||
metrics.RecordApprovalDecisionSubmitted(approval.WorkspaceId, normalizedDecision);
|
||||
|
||||
List<ApprovalDecision> decisions = await dbContext.ApprovalDecisions
|
||||
.Where(candidate => candidate.ApprovalRequestId == approval.Id)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
|
||||
namespace Socialize.Api.Modules.CalendarIntegrations.Services;
|
||||
|
||||
internal sealed class CalendarImportBackgroundService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
SocializeMetrics metrics,
|
||||
ILogger<CalendarImportBackgroundService> logger)
|
||||
: BackgroundService
|
||||
{
|
||||
@@ -22,6 +25,7 @@ internal sealed class CalendarImportBackgroundService(
|
||||
using IServiceScope scope = scopeFactory.CreateScope();
|
||||
CalendarImportSyncService syncService = scope.ServiceProvider.GetRequiredService<CalendarImportSyncService>();
|
||||
await syncService.RefreshDueSourcesAsync(stoppingToken);
|
||||
metrics.RecordBackgroundJobRun(nameof(CalendarImportBackgroundService), true);
|
||||
}
|
||||
catch (OperationCanceledException ex) when (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -30,6 +34,7 @@ internal sealed class CalendarImportBackgroundService(
|
||||
#pragma warning disable CA1031 // Background service should log and continue after unexpected sync failures.
|
||||
catch (Exception ex)
|
||||
{
|
||||
metrics.RecordBackgroundJobRun(nameof(CalendarImportBackgroundService), false);
|
||||
logger.LogError(ex, "Calendar import background sync failed.");
|
||||
}
|
||||
#pragma warning restore CA1031
|
||||
|
||||
@@ -2,6 +2,7 @@ using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.BlobStorage.Contracts;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.ContentItems.Contracts;
|
||||
using Socialize.Api.Modules.ContentItems.Data;
|
||||
@@ -34,7 +35,8 @@ internal class CreateCommentHandler(
|
||||
AccessScopeService accessScopeService,
|
||||
IBlobStorage blobStorage,
|
||||
IContentItemActivityWriter activityWriter,
|
||||
INotificationEventWriter notificationEventWriter)
|
||||
INotificationEventWriter notificationEventWriter,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<CreateCommentRequest, CommentDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -156,6 +158,7 @@ internal class CreateCommentHandler(
|
||||
|
||||
dbContext.Comments.Add(comment);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordCommentCreated(comment.WorkspaceId, comment.AttachmentBlobName is not null);
|
||||
|
||||
string? authorPortraitUrl = await dbContext.Users
|
||||
.Where(candidate => candidate.Id == comment.AuthorUserId)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.ContentItems.Contracts;
|
||||
using Socialize.Api.Modules.Notifications.Contracts;
|
||||
@@ -39,7 +40,8 @@ internal class CreateContentItemHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService,
|
||||
IContentItemActivityWriter activityWriter,
|
||||
INotificationEventWriter notificationEventWriter)
|
||||
INotificationEventWriter notificationEventWriter,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<CreateContentItemRequest, ContentItemDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -123,6 +125,7 @@ internal class CreateContentItemHandler(
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
});
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordContentItemCreated(item.WorkspaceId);
|
||||
|
||||
await activityWriter.WriteAsync(
|
||||
new ContentItemActivityWriteModel(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FastEndpoints;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Feedback.Contracts;
|
||||
using Socialize.Api.Modules.Feedback.Data;
|
||||
@@ -45,7 +46,8 @@ internal class SubmitFeedbackRequestValidator
|
||||
|
||||
internal class SubmitFeedbackHandler(
|
||||
AppDbContext dbContext,
|
||||
FeedbackNotificationService notificationService)
|
||||
FeedbackNotificationService notificationService,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<SubmitFeedbackRequest, FeedbackReportDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -93,6 +95,7 @@ internal class SubmitFeedbackHandler(
|
||||
dbContext.FeedbackReports.Add(report);
|
||||
await notificationService.AddNewReportNotificationsAsync(report, ct);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordFeedbackSubmitted(report.Type.ToString(), report.WorkspaceId);
|
||||
|
||||
await SendAsync(report.ToDto(), StatusCodes.Status201Created, ct);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Identity.Data;
|
||||
using Socialize.Api.Modules.Identity.Configuration;
|
||||
@@ -21,7 +22,8 @@ internal record LoginResponse(
|
||||
internal class LoginHandler(
|
||||
UserManager userManager,
|
||||
IOptionsSnapshot<JwtOptions> jwtOptions,
|
||||
AccessTokenFactory accessTokenFactory)
|
||||
AccessTokenFactory accessTokenFactory,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<LoginRequest, LoginResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -40,6 +42,7 @@ internal class LoginHandler(
|
||||
user ??= await userManager.FindByNameAsync(request.Email);
|
||||
if (user is null)
|
||||
{
|
||||
metrics.RecordLoginAttempt(false, "unknown_user");
|
||||
await SendStringAsync(
|
||||
"Invalid email or password",
|
||||
401,
|
||||
@@ -51,6 +54,7 @@ internal class LoginHandler(
|
||||
bool isPasswordValid = await userManager.CheckPasswordAsync(user, request.Password);
|
||||
if (!isPasswordValid)
|
||||
{
|
||||
metrics.RecordLoginAttempt(false, "invalid_password");
|
||||
await SendStringAsync(
|
||||
"Invalid email or password",
|
||||
401,
|
||||
@@ -61,6 +65,7 @@ internal class LoginHandler(
|
||||
// Check if the email is confirmed
|
||||
if (!user.EmailConfirmed)
|
||||
{
|
||||
metrics.RecordLoginAttempt(false, "email_unconfirmed");
|
||||
await SendStringAsync(
|
||||
"Email not verified. Please check your email for verification instructions.",
|
||||
401,
|
||||
@@ -76,6 +81,7 @@ internal class LoginHandler(
|
||||
|
||||
// Generate JWT token
|
||||
string accessToken = await accessTokenFactory.CreateAsync(user);
|
||||
metrics.RecordLoginAttempt(true, "success");
|
||||
|
||||
await SendOkAsync(
|
||||
new LoginResponse(accessToken, user.RefreshToken),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Organizations.Data;
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
@@ -21,7 +22,8 @@ internal class CreateOrganizationRequestValidator
|
||||
}
|
||||
|
||||
internal class CreateOrganizationHandler(
|
||||
AppDbContext dbContext)
|
||||
AppDbContext dbContext,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<CreateOrganizationRequest, OrganizationDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -66,6 +68,7 @@ internal class CreateOrganizationHandler(
|
||||
dbContext.Organizations.Add(organization);
|
||||
dbContext.OrganizationMemberships.Add(ownerMembership);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordOrganizationCreated(organization.Id);
|
||||
|
||||
await SendAsync(
|
||||
OrganizationDto.FromOrganization(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Modules.ReleaseCommunications.Configuration;
|
||||
|
||||
namespace Socialize.Api.Modules.ReleaseCommunications.Services;
|
||||
@@ -6,6 +7,7 @@ namespace Socialize.Api.Modules.ReleaseCommunications.Services;
|
||||
internal sealed class ReleaseUpdateEmailDigestBackgroundService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<ReleaseCommunicationEmailOptions> options,
|
||||
SocializeMetrics metrics,
|
||||
ILogger<ReleaseUpdateEmailDigestBackgroundService> logger)
|
||||
: BackgroundService
|
||||
{
|
||||
@@ -42,6 +44,7 @@ internal sealed class ReleaseUpdateEmailDigestBackgroundService(
|
||||
TimeSpan.FromHours(options.Value.DigestIntervalHours),
|
||||
force: false,
|
||||
ct: stoppingToken);
|
||||
metrics.RecordBackgroundJobRun(nameof(ReleaseUpdateEmailDigestBackgroundService), true);
|
||||
if (sentCount > 0 && logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
logger.LogInformation("Sent {SentCount} release update digest emails.", sentCount);
|
||||
@@ -54,6 +57,7 @@ internal sealed class ReleaseUpdateEmailDigestBackgroundService(
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception ex)
|
||||
{
|
||||
metrics.RecordBackgroundJobRun(nameof(ReleaseUpdateEmailDigestBackgroundService), false);
|
||||
logger.LogError(ex, "Release update digest service failed.");
|
||||
}
|
||||
#pragma warning restore CA1031
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
@@ -24,7 +25,8 @@ internal class CreateWorkspaceRequestValidator
|
||||
|
||||
internal class CreateWorkspaceHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService)
|
||||
AccessScopeService accessScopeService,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<CreateWorkspaceRequest, WorkspaceDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -65,6 +67,7 @@ internal class CreateWorkspaceHandler(
|
||||
|
||||
dbContext.Workspaces.Add(workspace);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordWorkspaceCreated(workspace.OrganizationId, workspace.Id);
|
||||
|
||||
WorkspaceDto dto = WorkspaceDto.FromWorkspace(workspace, []);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Observability;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Identity.Contracts;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
@@ -31,7 +32,8 @@ internal class CreateWorkspaceInviteRequestValidator
|
||||
|
||||
internal class CreateWorkspaceInviteHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService)
|
||||
AccessScopeService accessScopeService,
|
||||
SocializeMetrics metrics)
|
||||
: Endpoint<CreateWorkspaceInviteRequest, WorkspaceInviteDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -91,6 +93,7 @@ internal class CreateWorkspaceInviteHandler(
|
||||
|
||||
dbContext.WorkspaceInvites.Add(invite);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
metrics.RecordWorkspaceInviteCreated(invite.WorkspaceId, invite.Role);
|
||||
|
||||
await SendAsync(
|
||||
new WorkspaceInviteDto(
|
||||
|
||||
Reference in New Issue
Block a user