iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Modern Web

擁抱 .Net Core系列 第 17

[Day17] .Net Core 中的Log - 3

  • 分享至 

  • xImage
  •  

接續昨天說的Log 三劍客

ILogger

public interface ILogger
{
    void Log<TState>(
      LogLevel logLevel,
      EventId eventId,
      TState state,
      Exception? exception,
      Func<TState, Exception?, string> formatter);

    bool IsEnabled(LogLevel logLevel);

    IDisposable BeginScope<TState>(TState state);
}

Log

可以看到Ilogger 介面只有一個log方法
....修但幾類,昨天不是看還有看到什麼 LogWarning 的方法嗎
這邊講的是ILoggerFactory建立的ILogger
其實這些方法放在LoggerExtensions.cs
而且最後都會還是會呼叫ILogger.Log

那我們使用extenstion看到的那串參數呢,是塞到哪裡去了
不管是在文字檔中或是Console 中顯示的log ,其實都是格式化過的字串
針對不同類型(TState)的參數,你可能會想要透過不同的方式去log他。
舉例而言,A類型的log 你可能會想要透過json的方式輸出
B類型的log只要記錄時間跟發生的class nmae
但就可以透過fomatter去達到你想要的格式

另外以呼叫LoggerExtensionLog方法的時候
最後的message + arg[] 會轉成一個 FormattedLogValues的物件
當想要顯示字串的時候,只要呼叫ToString()即可
這邊就跟昨天的伏筆有一點關係
昨天的範例

logger.Log(LogLevel.Error, "error with compiled time constant message: {ErrorMessage}", errorMessage);
// 產生的FormattedLogValues: new FormattedLogValues("error with compiled time constant message: {ErrorMessage}",errorMessage)

logger.Log(LogLevel.Critical, $"error with message: {errorMessage}");

這邊只節錄一點,完整程式碼
FormattedLogValues.cs

internal readonly struct FormattedLogValues : IReadOnlyList<KeyValuePair<string, object?>>
{
    private static ConcurrentDictionary<string, LogValuesFormatter> _formatters = new ConcurrentDictionary<string, LogValuesFormatter>();
    private readonly LogValuesFormatter? _formatter;
    internal const int MaxCachedFormatters = 1024;
    private static int _count;
    private readonly string _originalMessage;
    
    public FormattedLogValues(string? format, params object?[]? values)
    {
        if (values != null && values.Length != 0 && format != null)
        {
            if (_count >= MaxCachedFormatters)
            {
                if (!_formatters.TryGetValue(format, out _formatter))
                {
                    _formatter = new LogValuesFormatter(format);
                }
            }
            else
            {
                _formatter = _formatters.GetOrAdd(format, f =>
                {
                    Interlocked.Increment(ref _count);
                    return new LogValuesFormatter(f);
                });
            }
        }
        else
        {
            _formatter = null;
        }
        
         _originalMessage = format ?? NullFormat;
    }
    
     public override string ToString()
    {
        if (_formatter == null)
        {
            return _originalMessage;
        }

        return _formatter.Format(_values);
    }
}

使用FormatString 的方式記錄的時候(前者),會產生一個 LogValuesFormatter 的物件,他的建構式其實作了很多事,所以該物件會根據範本存在快取中。

後者相對單純,會直接回傳該string

以效能來看,我全部都用後者的方式記錄就好啦,為何使用前者
如果最後輸出使用的是日誌型的log Ex.NLog 的話確實沒差
但是如果是結構型的日誌的話,在查找log的時候,前者會方便很多
還記得我們記log的原因嗎
就是為了查找問題
透過好的log 作法,可以 快速找出問題=>快速解決問題=>早點下班(並沒有)
可以參考問答
我有空再來寫一篇文章來介紹

BeginScope

當我們在一個Request中,AService被BService呼叫的時候
我們會想要識別AService 跟 BService 的來源是相同的
所以就有了Scope的存在
沒有使用Scope的情況

var serviceCollection = new ServiceCollection()
    // add log and configuration console providers
    .AddLogging(builder => builder.AddSimpleConsole(options =>
    {
        options.TimestampFormat = "[HH:mm:ss]";
        options.SingleLine = true;
        options.ColorBehavior = LoggerColorBehavior.Enabled;
    }))
    .AddScoped<ServiceA>()
    .AddScoped<ServiceB>();

var serviceProvider = serviceCollection.BuildServiceProvider();

var serviceB = serviceProvider.GetRequiredService<ServiceB>();

serviceB.DoSomething();
Console.Read();


class ServiceA
{
    private readonly ILogger<ServiceA> _logger;

    public ServiceA(ILogger<ServiceA> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        _logger.LogInformation("Service A is doing something");
    }
}

class ServiceB
{
    private readonly ILogger<ServiceB> _logger;
    private readonly ServiceA _serviceA;

    public ServiceB(ILogger<ServiceB> logger, ServiceA serviceA)
    {
        _logger = logger;
        _serviceA = serviceA;
    }

    public void DoSomething()
    {
        _logger.LogInformation("Service B is doing something");
        _serviceA.DoSomething();
    }
}

https://ithelp.ithome.com.tw/upload/images/20220928/20109549qtEkbxdRZI.png

使用Scope,可以清楚的看見誰呼叫誰

var serviceCollection = new ServiceCollection()
    // add log and configuration console providers
    .AddLogging(builder => builder.AddSimpleConsole(options =>
    {
        options.TimestampFormat = "[HH:mm:ss]";
        options.SingleLine = true;
        //// 顯示Scope
        options.IncludeScopes = true;
        options.ColorBehavior = LoggerColorBehavior.Enabled;
    }))
    .AddScoped<ServiceA>()
    .AddScoped<ServiceB>();

var serviceProvider = serviceCollection.BuildServiceProvider();

var serviceB = serviceProvider.GetRequiredService<ServiceB>();

serviceB.DoSomething();
Console.Read();


class ServiceA
{
    private readonly ILogger<ServiceA> _logger;

    public ServiceA(ILogger<ServiceA> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        using (_logger.BeginScope("Service A"))
        {
            _logger.LogInformation("Service A is doing something");
        }
    }
}

class ServiceB
{
    private readonly ILogger<ServiceB> _logger;
    private readonly ServiceA _serviceA;

    public ServiceB(ILogger<ServiceB> logger, ServiceA serviceA)
    {
        _logger = logger;
        _serviceA = serviceA;
    }

    public void DoSomething()
    {
        using (_logger.BeginScope("Service B"))
        {
            _logger.LogInformation("Service B is doing something");
            _serviceA.DoSomething();
        }
    }
}

https://ithelp.ithome.com.tw/upload/images/20220929/20109549vSryyIH6o2.png


上一篇
[Day16] .Net Core 中的Log - 2
下一篇
[Day17] .Net Core 中的Log - 4
系列文
擁抱 .Net Core30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言