2022. 12. 29. 00:00ㆍASPNET/ASPNET 7
이제 지금까지 만든 Template 에서 Logging 을 해보자
이전에 Serilog 를 통해 file 에 Log 를 쓸수 있도록 했다.
이제 특정 format 은 만들어서 Logging 에 특정 패턴을 적용해보자
serilog 자체의 pattern 이 있긴 하지만 호출한 함수 이름을 제대로 파악하지 못하는 문제들이 있다.
그래서 ILogger 를 composit 하고 있는 IApiLogger 를 이용하여 Log 를 남겨 볼것이다.
Share/Interfaces/IApiLogger.cs
using ApiServer.Shared.Injectables;
using System.Runtime.CompilerServices;
namespace ApiServer.Shared.Interfaces;
public interface IApiLogger:ITransient
{
void Log(LogLevel logLevel, string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogTrace(string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogDebug(string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogInformation(string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogWarning(string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogError(string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
void LogError(Exception exception, string message,
[CallerMemberName] string membername = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
params object[] args);
}
Share/Utility/ApiLogger.cs
using ApiServer.Shared.Interfaces;
using System.Runtime.CompilerServices;
namespace ApiServer.Shared.Utility;
public class ApiLogger : IApiLogger
{
private ILogger _logger;
public ApiLogger(ILogger<ApiLogger> logger) => _logger = logger;
public void Log(LogLevel logLevel, string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.Log(logLevel, message, membername, filePath, lineNumber, args);
}
public void LogDebug(string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogDebug(
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
public void LogError(string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogError(
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
public void LogError(Exception exception, string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogError(exception,
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
public void LogInformation(string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogInformation(
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
public void LogTrace(string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogTrace(
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
public void LogWarning(string message, [CallerMemberName] string membername = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, params object[] args)
{
_logger.LogWarning(
CreateLogMessage(message, membername, filePath, lineNumber), args);
}
private string CreateLogMessage(string message,
string memberName, string filePath, int lineNumber) =>
$"[{memberName}] {message}. \nfilePath = {filePath} : line = {lineNumber}";
}
자 일단 기본적인 Logger 에 대한 customize 는 완료했다.
이제 Web api 를 외부에서 호출 하면 request 와 response 를 자동으로 로깅하도록 만들어보자
우린 Middleware 를 이용하여 자동 로깅을 구현할 것이다.
Shared/Middlewares/AutoLogger.cs
using ApiServer.Shared.Interfaces;
using System.Text;
namespace ApiServer.Shared.Middlewares;
public class AutoLogger
{
private readonly RequestDelegate _next;
private readonly IApiLogger _logger;
public AutoLogger(RequestDelegate next, IApiLogger logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var request = await FormatRequest(context.Request);
_logger.LogDebug(request);
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
var response = await FormatResponse(context.Response);
_logger.LogDebug(response);
await responseBody.CopyToAsync(originalBodyStream);
}
}
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableBuffering();
var body = request.Body;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
body.Seek(0, SeekOrigin.Begin);
request.Body = body;
return $"{request.Scheme} endpoint = {request.Host}{request.Path} ," +
$"query = {request.QueryString} ," +
$"body = {bodyAsText}";
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"{response.StatusCode}: {text}";
}
}
Program.cs
using ApiServer.Shared.Middlewares;
...
app.UseMiddleware<AutoLogger>();
여기까지만 해도 api 에 대한 Trace 를 된다고 볼수 있다.
만약 MediatR 을 이용해서 http 단이 아닌 MediatR 단에서 처리 하고 싶다면 다음을 추가하자
Share/Behaviors/MediatRAutoLogger.cs
using ApiServer.Shared.Interfaces;
using MediatR;
using System.Diagnostics;
using System.Text.Json;
namespace ApiServer.Shared.Behaviors;
public class MediatRAutoLogger<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : MediatR.IRequest<TResponse>
{
private readonly IApiLogger _logger;
public MediatRAutoLogger(IApiLogger logger)
{
_logger = logger;
}
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
string requestName = typeof(TRequest).Name;
string uniqueId = Guid.NewGuid().ToString();
_logger.LogDebug($"Begin Request Id:{uniqueId}, request name:{requestName},\nRequest={JsonSerializer.Serialize(request, new JsonSerializerOptions { WriteIndented = true })}");
var timer = new Stopwatch();
timer.Start();
var response = next();
timer.Stop();
_logger.LogDebug($"End Request Id:{uniqueId}, request name:{requestName},\nResponse={JsonSerializer.Serialize(response.Result, new JsonSerializerOptions { WriteIndented = true })}" +
$"\ntotal elapsed time: {timer.ElapsedMilliseconds}");
return response;
}
}
Program.cs
using ApiServer.Shared.Behaviors;
...
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MediatRAutoLogger<,>));
LogDebug 로 관련 Log 들을 적도록 되어 있기 때문에 appsettings.json 의 level 을 바꿔주자
Serilog:MinimumLevel:Default : "Debug"
그럼 화면에 Log 가 표시될 것이다.
관련영상
'ASPNET > ASPNET 7' 카테고리의 다른 글
Rest API Template 만들기 - EP 06 (Authentication, Authorization with jwt) (0) | 2023.01.05 |
---|---|
Rest API Template 만들기 - EP 05(AccessToken, RefreshToken with JWT) (0) | 2023.01.02 |
Rest API Template 만들기 - EP 03 (Dependency Injection, Scrutor) (0) | 2022.12.26 |
Rest API Template 만들기 - EP 02 (Vertical Slice, MediatR) (0) | 2022.12.22 |
Rest API Template 만들기 - EP 01 (Create , Swagger, Logging) (0) | 2022.12.19 |