Add project files.

This commit is contained in:
Sergio Matias Urquin
2025-04-29 18:42:29 -06:00
parent 9c1958d351
commit 83fc1878c4
67 changed files with 4586 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
// ***********************************************************************
// <copyright file="HttpErrorMiddleware.cs">
// Heath
// </copyright>
// ***********************************************************************
using Microsoft.AspNetCore.Http;
using Serilog;
using System.Text.Json;
namespace Core.Blueprint.Logging
{
/// <summary>
/// Handles HTTP logging.
/// </summary>
public class HttpErrorMiddleware
{
private readonly ILogger logger;
private readonly RequestDelegate requestProcess;
public readonly ServiceSettings settings;
/// <summary>
/// Creates a new instrance of <see cref="HttpErrorMiddleware"/>.
/// </summary>
/// <param name="logger">The logger representig an instance of <see cref="ILogger"/>.</param>
/// <param name="requestProcess">The request delegate process.</param>
public HttpErrorMiddleware(ILogger logger, RequestDelegate requestProcess, ServiceSettings settings)
{
this.logger = logger;
this.requestProcess = requestProcess;
this.settings = settings;
}
/// <summary>
/// Invoke method.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Invoke(HttpContext context)
{
try
{
await requestProcess(context).ConfigureAwait(false);
}
catch (HttpException exception)
{
await HandleErrorResponse(
context,
exception.Message,
exception.ErrorCode,
exception.StatusCode).ConfigureAwait(false);
}
catch (Exception defaultException)
{
await HandleErrorResponse(
context,
defaultException.Message,
ErrorCodes.InternalServerError,
StatusCodes.Status500InternalServerError).ConfigureAwait(false);
}
}
/// <summary>
/// Handles error responses.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <param name="message">The error message.</param>
/// <param name="errorCode">The error code.</param>
/// <param name="statusCode">The HTTP status code.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task HandleErrorResponse(HttpContext context, string? message, string? errorCode, int statusCode)
{
var errorMessage = new HttpError(
message,
errorCode,
string.Format(
Responses.Target,
context.Request.Method,
context.Request.Scheme,
context.Request.Host.Host,
context.Request.Path));
logger.LogError<HttpError>(context, errorMessage, $"{settings.ApplicationName}-{settings.LayerName}");
context.Response.ContentType = MimeTypes.ApplicationJson;
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(JsonSerializer.Serialize(errorMessage)).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,237 @@
// ***********************************************************************
// <copyright file="HttpLogger.cs">
// Heath
// </copyright>
// ***********************************************************************
using Microsoft.AspNetCore.Http;
using Microsoft.IO;
using Serilog;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Core.Blueprint.Logging
{
/// <summary>
/// Handles all logging scenarios.
/// </summary>
public static class HttpLogger
{
/// <summary>
/// The JSON serializer options for logging methods.
/// </summary>
public static JsonSerializerOptions serializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = {
new JsonStringEnumConverter( JsonNamingPolicy.CamelCase),
},
};
/// <summary>
/// Logs an error message.
/// </summary>
/// <typeparam name="TMessage">The generic message parameter.</typeparam>
/// <param name="context">The HTTP context.</param>
/// <param name="message">The message.</param>
/// <param name="serviceId">The service identifier.</param>
public static void LogError<TMessage>(this ILogger logger, HttpContext context, TMessage message, string? serviceId)
{
var logMessage = CreateErrorLog(context, message, serviceId);
logger.Error(logMessage);
}
/// <summary>
/// Logs an information message.
/// </summary>
/// <typeparam name="TMessage">The generic message parameter.</typeparam>
/// <param name="context">The HTTP context.</param>
/// <param name="message">The message.</param>
/// <param name="serviceId">The service identifier.</param>
public static void LogInfo<TMessage>(this ILogger logger, HttpContext context, TMessage message, string? serviceId)
{
var logMessage = CreateInfoLog(context, message, serviceId);
logger.Information(logMessage);
}
/// <summary>
/// Logs an incoming HTTP request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="context">The HTTP context.</param>
/// <param name="recyclableMemoryStreamManager">The recyclable mmory stream manager.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task LogRequest(
this ILogger logger,
HttpContext context,
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
string? serviceId)
{
context.Request.EnableBuffering();
await using var requestStream = recyclableMemoryStreamManager.GetStream();
await context.Request.Body.CopyToAsync(requestStream);
var logMessage = CreateRequestLog(
context,
ReadStream(requestStream),
serviceId);
logger.Information(logMessage);
context.Request.Body.Position = 0;
}
/// <summary>
/// Logs an outcome HTTP response.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="context">The HTTP context.</param>
/// <param name="recyclableMemoryStreamManager">The recyclable mmory stream manager.</param>
/// <param name="requestProcess">The request delegate process.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
///
public static async Task LogResponse(
this ILogger logger,
HttpContext context,
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
RequestDelegate requestProcess,
string? serviceId)
{
var originalBodyStream = context.Response.Body;
await using var responseBody = recyclableMemoryStreamManager.GetStream();
context.Response.Body = responseBody;
await requestProcess(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Seek(0, SeekOrigin.Begin);
var logMessage = CreateResponseLog(context, text, serviceId);
logger.Information(logMessage);
await responseBody.CopyToAsync(originalBodyStream);
}
/// <summary>
/// Creates an error log.
/// </summary>
/// <typeparam name="TMessage">The generic message.</typeparam>
/// <param name="context">The HTTP context.</param>
/// <param name="message">The error message.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="string"/> representig the error log.</returns>
private static string CreateErrorLog<TMessage>(HttpContext context, TMessage message, string? serviceId)
=> CreateLog(context, LogSeverity.Error, LogOperation.Error, message, serviceId);
/// <summary>
/// Creates an info log.
/// </summary>
/// <typeparam name="TMessage">The generic message.</typeparam>
/// <param name="context">The HTTP context.</param>
/// <param name="message">The info message.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="string"/> representig the info log.</returns>
private static string CreateInfoLog<TMessage>(HttpContext context, TMessage message, string? serviceId)
=> CreateLog(context, LogSeverity.Info, LogOperation.Info, message, serviceId);
/// <summary>
/// Creates a request log.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <param name="requestBody">The request body.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="string"/> representig the request log.</returns>
private static string CreateRequestLog(HttpContext context, string? requestBody, string? serviceId)
=> CreateLog(context, LogSeverity.Info, LogOperation.ClientRequest, requestBody, serviceId);
/// <summary>
/// Creates a response log.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <param name="responseBody">The response body.</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="string"/> representig the response log.</returns>
private static string CreateResponseLog(HttpContext context, string? responseBody, string? serviceId)
=> CreateLog(context, LogSeverity.Info, LogOperation.ClientResponse, responseBody, serviceId);
/// <summary>
/// Creates a generic log.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <param name="severity">The log severity.</param>
/// <param name="operation">The log operation.</param>
/// <param name="message">The log message</param>
/// <param name="serviceId">The service identifier.</param>
/// <returns>A <see cref="string"/> representing a generic log.</returns>
private static string CreateLog<TMessage>(
HttpContext context,
LogSeverity severity,
LogOperation operation,
TMessage message,
string? serviceId)
{
var tokenHeader = context.Request.Headers[Headers.Authorization].FirstOrDefault()?.Split(" ").Last();
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHeader is not null ? tokenHandler.ReadJwtToken(tokenHeader) : null;
var log = new LogDetail<TMessage>
{
Severity = severity,
Target = new LogTarget
{
Method = context.Request.Method,
Host = context.Request.Host.Host,
Route = context.Request.Path,
},
Email = token?.Claims.FirstOrDefault(c => c.Type == Claims.Email)?.Value,
User = token?.Claims.FirstOrDefault(c => c.Type == Claims.Name)?.Value,
UserId = token?.Claims.FirstOrDefault(c => c.Type == Claims.Id)?.Value,
Environment = Environment.GetEnvironmentVariable(EnvironmentVariables.Stage),
Operation = operation,
RequestId = context.Request.Headers[DisplayNames.RequestId],
ServiceId = serviceId,
XForwardedFor = context.Request.Headers[DisplayNames.XForwardedForHeader],
Timestamp = DateTime.Now,
Message = message,
};
var serializedLog = JsonSerializer.Serialize(log, serializerOptions);
return serializedLog
.Replace("\\u0022", "\"")
.Replace("\"{", "{")
.Replace("}\"", "}")
.Replace("\\u0027", "'")
.Replace("\\\u0027", "'")
.Replace("\n", "");
}
/// <summary>
/// Reads the stream.
/// </summary>
/// <param name="stream">The stream to be read.</param>
/// <returns>A <see cref="string?"/> representig the request body.</returns>
private static string? ReadStream(Stream stream)
{
const int readChunkBufferLength = 4096;
stream.Seek(0, SeekOrigin.Begin);
using var textWriter = new StringWriter();
using var reader = new StreamReader(stream);
var readChunk = new char[readChunkBufferLength];
int readChunkLength;
do
{
readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength);
textWriter.Write(readChunk, 0, readChunkLength);
} while (readChunkLength > 0);
var stringItem = textWriter.ToString();
return stringItem != string.Empty ? stringItem : null;
}
}
}

View File

@@ -0,0 +1,69 @@
// ***********************************************************************
// <copyright file="HttpLoggingMiddleware.cs">
// Heath
// </copyright>
// ***********************************************************************
using Microsoft.AspNetCore.Http;
using Microsoft.IO;
using Serilog;
namespace Core.Blueprint.Logging
{
/// <summary>
/// Handles HTTP logging.
/// </summary>
public class HttpLoggingMiddleware
{
private readonly ILogger logger;
private readonly RequestDelegate requestProcess;
private readonly ServiceSettings settings;
private readonly RecyclableMemoryStreamManager recyclableMemoryStreamManager;
/// <summary>
/// Creates a new instrance of <see cref="HttpLoggingMiddleware"/>.
/// </summary>
/// <param name="requestProcess">The request delegate process.</param>
/// <param name="logger">The logger representig an instance of <see cref="ILogger"/>.</param>
/// <param name="settings">The service settings.</param>
public HttpLoggingMiddleware(RequestDelegate requestProcess, ILogger logger, ServiceSettings settings)
{
this.logger = logger;
this.requestProcess = requestProcess;
this.settings = settings;
recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
}
/// <summary>
/// Invoke method.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
await LogRequest(context);
await LogResponse(context);
}
/// <summary>
/// Logs an incoming HTTP request.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task LogRequest(HttpContext context)
{
await logger.LogRequest(context, recyclableMemoryStreamManager, $"{settings.ApplicationName}-{settings.LayerName}");
}
/// <summary>
/// Logs an outcome HTTP response.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task LogResponse(HttpContext context)
{
await logger.LogResponse(context, recyclableMemoryStreamManager, requestProcess, $"{settings.ApplicationName}-{settings.LayerName}");
}
}
}