diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Constants/Headers.cs index bc9fbee..ada1320 100644 --- a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Constants/Headers.cs +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Constants/Headers.cs @@ -8,4 +8,5 @@ public static class Headers public const string Pagination = "X-Pagination"; public const string Authorization = "Authorization"; public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; } diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/ClientsExtension.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/ClientsExtension.cs index 09f6e15..3cf5108 100644 --- a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/ClientsExtension.cs +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/ClientsExtension.cs @@ -9,6 +9,7 @@ public static void AddHttpClients(this IServiceCollection services) // registers the header propagation service // essential for receiving an authenticated request and forwarding it to another service + services.AddTransient(); services.AddHeaderPropagation(options => { options.Headers.Add(Headers.Authorization); @@ -63,14 +64,27 @@ public static void AddHttpClients(this IServiceCollection services) }); customersClient.AddHeaderPropagation(); + customersClient.AddHttpMessageHandler(); + ownersClient.AddHeaderPropagation(); + ownersClient.AddHttpMessageHandler(); paymentsClient.AddHeaderPropagation(); + paymentsClient.AddHttpMessageHandler(); + storesClient.AddHeaderPropagation(); + storesClient.AddHttpMessageHandler(); + productsClient.AddHeaderPropagation(); + productsClient.AddHttpMessageHandler(); subscriptionsClient.AddHeaderPropagation(); + subscriptionsClient.AddHttpMessageHandler(); + ordersClient.AddHeaderPropagation(); + ordersClient.AddHttpMessageHandler(); + credentialsClient.AddHeaderPropagation(); + credentialsClient.AddHttpMessageHandler(); } } diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/HttpPipelineExtension.cs index 90263a5..0238d60 100644 --- a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Extensions/HttpPipelineExtension.cs @@ -15,6 +15,8 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthorization(); app.UsePrincipalMiddleware(); + app.UseCorrelationMiddleware(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Interceptors/CorrelationInterceptor.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Interceptors/CorrelationInterceptor.cs new file mode 100644 index 0000000..a1bd5a4 --- /dev/null +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Interceptors/CorrelationInterceptor.cs @@ -0,0 +1,17 @@ +namespace Comanda.Orchestrator.WebApi.Interceptors; + +public sealed class CorrelationInterceptor(IHttpContextAccessor accessor) : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var correlation = accessor.HttpContext?.Items[Headers.Correlation]?.ToString(); + + if (!string.IsNullOrWhiteSpace(correlation)) + { + request.Headers.Remove(Headers.Correlation); + request.Headers.Add(Headers.Correlation, correlation); + } + + return await base.SendAsync(request, cancellationToken); + } +} diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..a1e0b80 --- /dev/null +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Orchestrator.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..d6802ee --- /dev/null +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Orchestrator.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Usings.cs b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Usings.cs index a9d8dd2..11ce87d 100644 --- a/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Usings.cs +++ b/Boundaries/Comanda.Orchestrator/Source/Comanda.Orchestrator.WebApi/Usings.cs @@ -21,6 +21,7 @@ global using Comanda.Orchestrator.WebApi.Extensions; global using Comanda.Orchestrator.WebApi.Constants; global using Comanda.Orchestrator.WebApi.Middlewares; +global using Comanda.Orchestrator.WebApi.Interceptors; global using Comanda.Orchestrator.WebApi.Providers; global using Comanda.Orchestrator.Application.Payloads.Payments; @@ -38,5 +39,6 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore; diff --git a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Constants/Headers.cs new file mode 100644 index 0000000..43da8c4 --- /dev/null +++ b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Constants/Headers.cs @@ -0,0 +1,12 @@ +namespace Comanda.Orders.WebApi.Constants; + +public static class Headers +{ + public const string Credential = "X-Credential"; + public const string Location = "Location"; + public const string Link = "Link"; + public const string Pagination = "X-Pagination"; + public const string Authorization = "Authorization"; + public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; +} diff --git a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Extensions/HttpPipelineExtension.cs index 39483d0..ca516ff 100644 --- a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Extensions/HttpPipelineExtension.cs @@ -13,6 +13,7 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthentication(); app.UseAuthorization(); + app.UseCorrelationMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..e730124 --- /dev/null +++ b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Orders.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..b35efe3 --- /dev/null +++ b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Orders.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Usings.cs b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Usings.cs index 084da95..88a3508 100644 --- a/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Usings.cs +++ b/Boundaries/Comanda.Orders/Source/Comanda.Orders.WebApi/Usings.cs @@ -8,6 +8,7 @@ global using Comanda.Orders.WebApi.Extensions; global using Comanda.Orders.WebApi.Constants; +global using Comanda.Orders.WebApi.Middlewares; global using Comanda.Orders.Domain.Errors; global using Comanda.Orders.Application.Payloads.Order; @@ -21,4 +22,5 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore; diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Constants/Headers.cs index c85d3bf..1a2f2f0 100644 --- a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Constants/Headers.cs +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Constants/Headers.cs @@ -3,4 +3,10 @@ public static class Headers { public const string Credential = "X-Credential"; + public const string Location = "Location"; + public const string Link = "Link"; + public const string Pagination = "X-Pagination"; + public const string Authorization = "Authorization"; + public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; } diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpClientsExtension.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpClientsExtension.cs index d1ad45e..1a73605 100644 --- a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpClientsExtension.cs +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpClientsExtension.cs @@ -8,6 +8,7 @@ public static void AddHttpClients(this IServiceCollection services, ISettings se // register transient lifetime interceptors here // https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-parameters-handlers services.AddTransient(); + services.AddTransient(); var paymentClient = services.AddHttpClient(client => { @@ -16,5 +17,6 @@ public static void AddHttpClients(this IServiceCollection services, ISettings se }); paymentClient.AddHttpMessageHandler(); + paymentClient.AddHttpMessageHandler(); } } diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpPipelineExtension.cs index c9246bd..3b42dd8 100644 --- a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Extensions/HttpPipelineExtension.cs @@ -13,6 +13,7 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthentication(); app.UseAuthorization(); + app.UseCorrelationMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Interceptors/CorrelationInterceptor.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Interceptors/CorrelationInterceptor.cs new file mode 100644 index 0000000..55ef5ae --- /dev/null +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Interceptors/CorrelationInterceptor.cs @@ -0,0 +1,17 @@ +namespace Comanda.Payments.WebApi.Interceptors; + +public sealed class CorrelationInterceptor(IHttpContextAccessor accessor) : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var correlation = accessor.HttpContext?.Items[Headers.Correlation]?.ToString(); + + if (!string.IsNullOrWhiteSpace(correlation)) + { + request.Headers.Remove(Headers.Correlation); + request.Headers.Add(Headers.Correlation, correlation); + } + + return await base.SendAsync(request, cancellationToken); + } +} diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..20cb841 --- /dev/null +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Payments.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..47de542 --- /dev/null +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Payments.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Usings.cs b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Usings.cs index c6f6fe1..c3e6013 100644 --- a/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Usings.cs +++ b/Boundaries/Comanda.Payments/Source/Comanda.Payments.WebApi/Usings.cs @@ -8,6 +8,7 @@ global using Comanda.Payments.WebApi.Constants; global using Comanda.Payments.WebApi.Interceptors; global using Comanda.Payments.WebApi.Extensions; +global using Comanda.Payments.WebApi.Middlewares; global using Comanda.Payments.Domain.Errors; global using Comanda.Payments.Application.Payloads.Traceability; @@ -25,4 +26,5 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore; diff --git a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Constants/Headers.cs new file mode 100644 index 0000000..c953c87 --- /dev/null +++ b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Constants/Headers.cs @@ -0,0 +1,12 @@ +namespace Comanda.Profiles.WebApi.Constants; + +public static class Headers +{ + public const string Credential = "X-Credential"; + public const string Location = "Location"; + public const string Link = "Link"; + public const string Pagination = "X-Pagination"; + public const string Authorization = "Authorization"; + public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; +} diff --git a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Extensions/HttpPipelineExtension.cs index 0c3c5cd..064c982 100644 --- a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Extensions/HttpPipelineExtension.cs @@ -13,6 +13,7 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthentication(); app.UseAuthorization(); + app.UseCorrelationMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..86ceb4a --- /dev/null +++ b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Profiles.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..6fe1437 --- /dev/null +++ b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Profiles.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Usings.cs b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Usings.cs index bedd0bd..877e48a 100644 --- a/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Usings.cs +++ b/Boundaries/Comanda.Profiles/Source/Comanda.Profiles.WebApi/Usings.cs @@ -8,6 +8,7 @@ global using Comanda.Profiles.WebApi.Extensions; global using Comanda.Profiles.WebApi.Constants; +global using Comanda.Profiles.WebApi.Middlewares; global using Comanda.Profiles.Domain.Errors; global using Comanda.Profiles.Application.Payloads.Traceability; @@ -23,4 +24,5 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore; diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Constants/Headers.cs index d961467..202b6a4 100644 --- a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Constants/Headers.cs +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Constants/Headers.cs @@ -2,6 +2,11 @@ namespace Comanda.Stores.WebApi.Constants; public static class Headers { - public const string Authorization = "Authorization"; + public const string Credential = "X-Credential"; + public const string Location = "Location"; + public const string Link = "Link"; public const string Pagination = "X-Pagination"; + public const string Authorization = "Authorization"; + public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; } diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/HttpPipelineExtension.cs index 2c8ae87..d25a18b 100644 --- a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/HttpPipelineExtension.cs @@ -14,6 +14,7 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthentication(); app.UseAuthorization(); + app.UseCorrelationMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/ObservabilityExtension.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/ObservabilityExtension.cs index d6d8ad8..4411c30 100644 --- a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/ObservabilityExtension.cs +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Extensions/ObservabilityExtension.cs @@ -24,7 +24,6 @@ public static void AddObservability(this WebApplicationBuilder builder) options.Dsn = settings.Observability.SentryDsn; options.TracesSampleRate = 1.0; options.AttachStacktrace = true; - options.Debug = true; }); }); } diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..5295687 --- /dev/null +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Stores.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..851a84e --- /dev/null +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Stores.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Usings.cs b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Usings.cs index 9e5832f..57ccedd 100644 --- a/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Usings.cs +++ b/Boundaries/Comanda.Stores/Source/Comanda.Stores.WebApi/Usings.cs @@ -8,6 +8,7 @@ global using Comanda.Stores.WebApi.Extensions; global using Comanda.Stores.WebApi.Constants; +global using Comanda.Stores.WebApi.Middlewares; global using Comanda.Stores.Domain.Errors; global using Comanda.Stores.Application.Payloads.Establishment; @@ -29,4 +30,5 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore; diff --git a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Constants/Headers.cs b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Constants/Headers.cs new file mode 100644 index 0000000..df24b2b --- /dev/null +++ b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Constants/Headers.cs @@ -0,0 +1,12 @@ +namespace Comanda.Subscriptions.WebApi.Constants; + +public static class Headers +{ + public const string Credential = "X-Credential"; + public const string Location = "Location"; + public const string Link = "Link"; + public const string Pagination = "X-Pagination"; + public const string Authorization = "Authorization"; + public const string Idempotency = "Idempotency-Key"; + public const string Correlation = "Correlation"; +} diff --git a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Extensions/HttpPipelineExtension.cs b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Extensions/HttpPipelineExtension.cs index 75bea74..a5f536c 100644 --- a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Extensions/HttpPipelineExtension.cs +++ b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Extensions/HttpPipelineExtension.cs @@ -13,9 +13,10 @@ public static void UseHttpPipeline(this IApplicationBuilder app) app.UseAuthentication(); app.UseAuthorization(); + app.UseCorrelationMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } -} \ No newline at end of file +} diff --git a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddleware.cs b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddleware.cs new file mode 100644 index 0000000..2bc2043 --- /dev/null +++ b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddleware.cs @@ -0,0 +1,29 @@ +namespace Comanda.Subscriptions.WebApi.Middlewares; + +public sealed class CorrelationMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var correlationId = context.Request.Headers[Headers.Correlation].FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(correlationId)) + correlationId = context.TraceIdentifier; + + context.Items[Headers.Correlation] = correlationId; + context.Response.Headers[Headers.Correlation] = correlationId; + + /* enriches the logging scope with the current correlation identifier, ensuring all logs within this request pipeline share the same identifier */ + /* more details: https://microsoft.github.io/code-with-engineering-playbook/observability/correlation-id/ */ + + using (LogContext.PushProperty(Headers.Correlation, correlationId)) + using (SentrySdk.PushScope()) + { + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("correlation_id", correlationId); + }); + + await next(context); + } + } +} diff --git a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddlewareExtension.cs b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddlewareExtension.cs new file mode 100644 index 0000000..4f89005 --- /dev/null +++ b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Middlewares/CorrelationMiddlewareExtension.cs @@ -0,0 +1,10 @@ +namespace Comanda.Subscriptions.WebApi.Middlewares; + +[ExcludeFromCodeCoverage(Justification = "contains only dependency injection")] +public static class CorrelationMiddlewareExtension +{ + public static IApplicationBuilder UseCorrelationMiddleware(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } +} diff --git a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Usings.cs b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Usings.cs index 4cc7248..58b01e1 100644 --- a/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Usings.cs +++ b/Boundaries/Comanda.Subscriptions/Source/Comanda.Subscriptions.WebApi/Usings.cs @@ -6,6 +6,7 @@ global using Comanda.Subscriptions.WebApi.Extensions; global using Comanda.Subscriptions.WebApi.Constants; +global using Comanda.Subscriptions.WebApi.Middlewares; global using Comanda.Subscriptions.Domain.Errors; global using Comanda.Subscriptions.Application.Payloads.Traceability; @@ -19,4 +20,5 @@ global using Scalar.AspNetCore; global using Serilog; +global using Serilog.Context; global using FluentValidation.AspNetCore;