Add project files.
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpLogger.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Lib.Common.LoggingAPI.Service.Constants;
|
||||
using Lib.Common.LoggingAPI.Service.DataTransferObjects.Logger;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IO;
|
||||
using Serilog;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lib.Common.LoggingAPI.Service.Middleware.HttpLogger
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpLoggingMiddleware.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Lib.Common.LoggingAPI.Common.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IO;
|
||||
using Serilog;
|
||||
|
||||
namespace Lib.Common.LoggingAPI.Service.Middleware.HttpLogger
|
||||
{
|
||||
/// <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 this.logger.LogRequest(context, this.recyclableMemoryStreamManager, this.settings.ServiceId);
|
||||
}
|
||||
|
||||
/// <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 this.logger.LogResponse(context, recyclableMemoryStreamManager, requestProcess, this.settings.ServiceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpLoggingMiddlewareExtension.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Lib.Common.LoggingAPI.Common.Settings;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Lib.Common.LoggingAPI.Service.Middleware.HttpLogger
|
||||
{
|
||||
public static class HttpLoggingMiddlewareExtension
|
||||
{
|
||||
public static IApplicationBuilder UseCustomHttpLogging(this IApplicationBuilder builder, ServiceSettings settings)
|
||||
{
|
||||
return builder.UseMiddleware<HttpLoggingMiddleware>(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user