// ***********************************************************************
//
// Heath
//
// ***********************************************************************
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;
}
}
}