默认在 dotnet 里面框架提供了 Microsoft.Extensions.Logging 可以和依赖注入做日志框架,而有些业务,如需要自己定制日志行为,此时就需要定制日志
当初写一个类继承 ILogger 是做不到定制,需要再写一个类继承 ILoggerProvider 才好做定制
如以下的方法
public class CCloudConsoleLogProvider : ILoggerProvider { /// <inheritdoc /> public void Dispose() {
}
/// <inheritdoc /> public ILogger CreateLogger(string categoryName) { return new CCloudConsoleLogger(); }
class CCloudConsoleLogger : ILogger { // 忽略代码 } }
通过 DI 的注入,在注入之前先干掉其他的 ILoggerProvider 实例
services.AddLogging(builder =>{ builder.ClearProviders(); builder.AddProvider(new CCloudConsoleLogProvider());});
现在所有拿到的 ILogger 都是从 CCloudConsoleLogProvider 创建的
下面是我定制的符合 honeycomb log 输出格式的日志,输出内容如下
[2099-10-19 19:07:45.456][threadName][INFO][类:行数][_traceId:realTraceId][_userId:realUserId][tag:custom] 业务的日志/异常堆栈
全部代码
public class CCloudConsoleLogProvider : ILoggerProvider { /// <inheritdoc /> public void Dispose() {
}
/// <inheritdoc /> public ILogger CreateLogger(string categoryName) { return new CCloudConsoleLogger(categoryName); }
class CCloudConsoleLogger : ILogger { public CCloudConsoleLogger(string categoryName) { _categoryName = categoryName; }
class Empty : IDisposable { /// <inheritdoc /> public void Dispose() { } }
/// <inheritdoc /> public IDisposable BeginScope<TState>(TState state) { return new Empty(); }
/// <inheritdoc /> public bool IsEnabled(LogLevel logLevel) { return true; }
private readonly string _categoryName;
/// <inheritdoc /> public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { // [2099-10-19 19:07:45.456][threadName][INFO][类:行数][_traceId:realTraceId][_userId:realUserId][tag:custom] 业务的日志/异常堆栈 string message; if (typeof(TState) == typeof(CCloudLogInfo)) { var logInfo = state as CCloudLogInfo; Debug.Assert(logInfo != null, nameof(logInfo) + " != null"); logInfo.CategoryName = _categoryName; message = formatter(state, exception); } else { // [2099-10-19 19:07:45.456][threadName][INFO][类:行数][_traceId:realTraceId][_userId:realUserId][tag:custom] 业务的日志/异常堆栈 message = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}][{Thread.CurrentThread.Name}:{Thread.CurrentThread.ManagedThreadId}][{CCloudLogExtension.LogLevelToString(logLevel)}][{_categoryName}][-][-][-][EventId={eventId.Id}:{eventId.Name}] {formatter(state, exception)}"; }
Console.WriteLine(message); } } }
internal class CCloudLogInfo { public string CategoryName { set; get; }
public string ThreadName { get; set; } public int ThreadId { get; set; }
public string ClassFile { set; get; }
public int LineNumber { get; set; }
public string Message { set; get; }
public string TraceId { set; get; }
public string UserId { set; get; }
public string MemberName { set; get; }
public string[] Tags { set; get; }
public LogLevel LogLevel { set; get; } }
public static class CCloudLogExtension { public static void Error(this ILogger logger, string message, Exception exception = null, string traceId = null, string userId = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0, params string[] tags) { const LogLevel logLevel = LogLevel.Error; var logInfo = new CCloudLogInfo() { ClassFile = Path.GetFileName(sourceFilePath), ThreadId = Thread.CurrentThread.ManagedThreadId, ThreadName = Thread.CurrentThread.Name, LineNumber = sourceLineNumber, Message = message, TraceId = traceId, UserId = userId, MemberName = memberName, Tags = tags, LogLevel = logLevel };
logger.Log(logLevel, eventId: EmptyEventId, logInfo, exception, Formatter); }
public static void Warning(this ILogger logger, string message, Exception exception = null, string traceId = null, string userId = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0, params string[] tags) { const LogLevel logLevel = LogLevel.Warning; var logInfo = new CCloudLogInfo() { ClassFile = Path.GetFileNameWithoutExtension(sourceFilePath), ThreadId = Thread.CurrentThread.ManagedThreadId, ThreadName = Thread.CurrentThread.Name, LineNumber = sourceLineNumber, Message = message, TraceId = traceId, UserId = userId, MemberName = memberName, Tags = tags, LogLevel = logLevel };
logger.Log(logLevel, eventId: EmptyEventId, logInfo, exception, Formatter); }
// 为什么只有 Info 可以添加 Exception 不添加信息,因为如果是 Warning 和 Error 推荐写是哪个模块 public static void Info(this ILogger logger, Exception exception = null, string traceId = null, string userId = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0, params string[] tags) { // ReSharper disable ExplicitCallerInfoArgument Info(logger, null, exception, traceId, userId, memberName, sourceFilePath, sourceLineNumber, tags); // ReSharper restore ExplicitCallerInfoArgument }
public static void Info(this ILogger logger, string message, Exception exception = null, string traceId = null, string userId = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0, params string[] tags) { // 刚好在 Linux 下构建的在 Linux 下运行,而在 Windows 构建的库在 Windows 下执行。此时使用 GetFileNameWithoutExtension 能保持输入路径和解析相同 // 假定在 Windows 下构建而在 Linux 下构建,只是让路径变长而已,我相信咱的日志系统炸不了…… 或者说,炸了再说 // 炸了的解决方法是在 dotnet runtime\src\libraries\System.Private.CoreLib\src\System\IO\Path.cs 的 GetFileName 方法里面将 `PathInternal.IsDirectorySeparator(path[i])` 替换为实际需要的 \ 或 / 符号
const LogLevel logLevel = LogLevel.Information; var logInfo = new CCloudLogInfo() { ClassFile = Path.GetFileName(sourceFilePath), ThreadId = Thread.CurrentThread.ManagedThreadId, ThreadName = Thread.CurrentThread.Name, LineNumber = sourceLineNumber, Message = message, TraceId = traceId, UserId = userId, MemberName = memberName, Tags = tags, LogLevel = logLevel };
logger.Log(logLevel, eventId: EmptyEventId, logInfo, exception, Formatter); }
public static void Debug(this ILogger logger, string message, Exception exception = null, string traceId = null, string userId = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0, params string[] tags) { var logInfo = new CCloudLogInfo() { ClassFile = Path.GetFileName(sourceFilePath), ThreadId = Thread.CurrentThread.ManagedThreadId, ThreadName = Thread.CurrentThread.Name, LineNumber = sourceLineNumber, Message = message, TraceId = traceId, UserId = userId, MemberName = memberName, Tags = tags, LogLevel = LogLevel.Debug };
logger.Log(logInfo.LogLevel, eventId: EmptyEventId, logInfo, exception, Formatter); }
private static string Formatter(CCloudLogInfo logInfo, Exception exception) { // honeycomb-log // [2099-10-19 19:07:45.456][threadName][INFO][类:行数][_traceId:realTraceId][_userId:realUserId][tag:custom] 业务的日志/异常堆栈
const string empty = "-";
var traceMessage = string.IsNullOrEmpty(logInfo.TraceId) ? empty : $"_traceId:{logInfo.TraceId}";
var userMessage = string.IsNullOrEmpty(logInfo.UserId) ? empty : $"_userId:{logInfo.UserId}";
var logLevelMessage = LogLevelToString(logInfo.LogLevel);
var logInfoMessage = string.IsNullOrEmpty(logInfo.Message) ? exception?.Message : logInfo.Message;
return $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.sss}][{logInfo.ThreadName}:{logInfo.ThreadId}][{logLevelMessage}][{logInfo.ClassFile}:{logInfo.CategoryName}.{logInfo.MemberName}:{logInfo.LineNumber}][{traceMessage}][{userMessage}][tags:{string.Join(";", logInfo.Tags)}] {logInfoMessage} {exception?.ToString()}"; }
public static string LogLevelToString(LogLevel logLevel) => logLevel switch { LogLevel.Trace => "TRACE", LogLevel.Debug => "DEBUG", LogLevel.Information => "INFO", LogLevel.Warning => "WARNING", LogLevel.Error => "ERROR", LogLevel.Critical => "CRITICAL", LogLevel.None => "NONE", _ => "NONE" };
private static readonly EventId EmptyEventId = new EventId(); }

原文链接: http://blog.lindexi.com/post/dotnet-%E5%AE%9A%E5%88%B6-ILogger-%E5%AE%9E%E7%8E%B0
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。