Add project files.
This commit is contained in:
92
Core.Blueprint.Logging/Middleware/HttpErrorMiddleware.cs
Normal file
92
Core.Blueprint.Logging/Middleware/HttpErrorMiddleware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
237
Core.Blueprint.Logging/Middleware/HttpLogger.cs
Normal file
237
Core.Blueprint.Logging/Middleware/HttpLogger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
Core.Blueprint.Logging/Middleware/HttpLoggingMiddleware.cs
Normal file
69
Core.Blueprint.Logging/Middleware/HttpLoggingMiddleware.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user