// *********************************************************************** // // AgileWebs // // *********************************************************************** 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 { /// /// Handles all logging scenarios. /// public static class HttpLogger { /// /// The JSON serializer options for logging methods. /// public static JsonSerializerOptions serializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter( JsonNamingPolicy.CamelCase), }, }; /// /// Logs an error message. /// /// The generic message parameter. /// The HTTP context. /// The message. /// The service identifier. public static void LogError(this ILogger logger, HttpContext context, TMessage message, string? serviceId) { var logMessage = CreateErrorLog(context, message, serviceId); logger.Error(logMessage); } /// /// Logs an information message. /// /// The generic message parameter. /// The HTTP context. /// The message. /// The service identifier. public static void LogInfo(this ILogger logger, HttpContext context, TMessage message, string? serviceId) { var logMessage = CreateInfoLog(context, message, serviceId); logger.Information(logMessage); } /// /// Logs an incoming HTTP request. /// /// The logger. /// The HTTP context. /// The recyclable mmory stream manager. /// The service identifier. /// A representing the asynchronous operation. 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; } /// /// Logs an outcome HTTP response. /// /// The logger. /// The HTTP context. /// The recyclable mmory stream manager. /// The request delegate process. /// The service identifier. /// A representing the asynchronous operation. /// 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); } /// /// Creates an error log. /// /// The generic message. /// The HTTP context. /// The error message. /// The service identifier. /// A representig the error log. private static string CreateErrorLog(HttpContext context, TMessage message, string? serviceId) => CreateLog(context, LogSeverity.Error, LogOperation.Error, message, serviceId); /// /// Creates an info log. /// /// The generic message. /// The HTTP context. /// The info message. /// The service identifier. /// A representig the info log. private static string CreateInfoLog(HttpContext context, TMessage message, string? serviceId) => CreateLog(context, LogSeverity.Info, LogOperation.Info, message, serviceId); /// /// Creates a request log. /// /// The HTTP context. /// The request body. /// The service identifier. /// A representig the request log. private static string CreateRequestLog(HttpContext context, string? requestBody, string? serviceId) => CreateLog(context, LogSeverity.Info, LogOperation.ClientRequest, requestBody, serviceId); /// /// Creates a response log. /// /// The HTTP context. /// The response body. /// The service identifier. /// A representig the response log. private static string CreateResponseLog(HttpContext context, string? responseBody, string? serviceId) => CreateLog(context, LogSeverity.Info, LogOperation.ClientResponse, responseBody, serviceId); /// /// Creates a generic log. /// /// The HTTP context. /// The log severity. /// The log operation. /// The log message /// The service identifier. /// A representing a generic log. private static string CreateLog( 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 { 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", ""); } /// /// Reads the stream. /// /// The stream to be read. /// A representig the request body. 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; } } }