Rest API Template 만들기 - EP 04 (Logger Customize, Trace)

2022. 12. 29. 00:00ASPNET/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 가 표시될 것이다. 

 

관련영상

https://youtu.be/guxIy3c6BBs

 

반응형