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);
}
可以看到Ilogger
介面只有一個log方法
....修但幾類,昨天不是看還有看到什麼 LogWarning
的方法嗎
這邊講的是ILoggerFactory
建立的ILogger
其實這些方法放在LoggerExtensions.cs
中
而且最後都會還是會呼叫ILogger.Log
那我們使用extenstion看到的那串參數呢,是塞到哪裡去了
不管是在文字檔中或是Console 中顯示的log ,其實都是格式化過的字串
針對不同類型(TState)的參數,你可能會想要透過不同的方式去log他。
舉例而言,A類型的log 你可能會想要透過json的方式輸出
B類型的log只要記錄時間跟發生的class nmae
但就可以透過fomatter去達到你想要的格式
另外以呼叫LoggerExtension
的Log
方法的時候
最後的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 作法,可以 快速找出問題=>快速解決問題=>早點下班(並沒有)
可以參考問答
我有空再來寫一篇文章來介紹
當我們在一個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();
}
}
使用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();
}
}
}