几个月前在研究了一番Serilog的时候,就想写一篇博客记录一下这个日志框架,可惜忙过了头,给忘了。那么它的优势就不多介绍了,相信你点到这篇文章之前,已经看了不少博客,且没有头绪,哈哈😄
废话不多说,本文不介绍Serilog的基础用法,只介绍它的一个特殊用法,就是如何使用自定义分类日志,就是把日志记录到不同的业务文件夹,由于时间久远,所以就只贴关键代码啦,懒人教程。
首先把这俩引用加了,当然不一定是这个版本哈
var builder = WebApplication.CreateBuilder(args);
//关键代码,记得加
builder.Host.UseSerilog();
SerilogHelper.InitSerilog();
这里使用纯代码方式,非配置文件(不会用配置文件来分类,如有大佬知道,请赐教,感谢!)
请关注Log.ForContext方法和Serilog的过滤器,它是实现将日志分类的关键所在,这一点也是在官网看到的,提供了很好的灵感。
public class SerilogHelper
{
#region Serilog 相关设置
internal static string LogFilePath(string fileName) => $@"Logs/{fileName}/{DateTime.Now.Year}.{DateTime.Now.Month}/log_.log";
internal static readonly string seriCustomProperty = "seriPos";
internal static readonly string logOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}][{ThreadId}] {Message:lj}{NewLine}{Exception}";
internal static readonly long? fileSize = 31457280L;
/// <summary>
/// 初始化serilog
/// </summary>
public static void InitSerilog()
{
/*
WriteTo.File,可同步或异步,异步需要引用async包
path:默认路径是程序的bin目录+path参数,当然也可以写绝对路径,只需要写入参数就可以了
rollingInterval:创建文件的类别,可以是分钟,小时,天,月。 此参数可以让创建的log文件名 + 时间。例如log_20220202_001.log
fileSizeLimitBytes:文件大小限制,类型long,1024=1KB,31457280=30MB
rollOnFileSizeLimit:达到文件大小限制后,是否继续创建新文件,如log_20220202_001.log
outputTemplate:日志模板,可以自定义
retainedFileCountLimit:设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件,等于null时永远保留文件
restrictedToMinimumLevel:最小写入级别,接收器Sink的级别必须高于Logger的级别,比如Logger的默认级别为 Information,即便 Sink 重写日志级别为 LogEventLevel.Debug,也只能看到 Information
*/
Serilog.Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Default", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft", LogEventLevel.Error)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext()//记录相关上下文信息
.WriteTo.Console(restrictedToMinimumLevel: levelConsole, theme: Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme.Code, outputTemplate: logOutputTemplate)
//利用过滤器对输出文件按文件夹分类
//如果使用依赖注入ILogger记录日志,当需要记录到指定文件夹,需要传入seriPos,否则只记录到默认Default文件夹
//比如:Log.Information("{seriPos}:Test", LogType.Order);//LogType自定义枚举
.WriteTo.Logger(lg =>
{
lg.Filter.ByIncludingOnly(e =>
{
try
{
if (e.Properties.TryGetValue(seriCustomProperty, out var value))
{
ScalarValue scalarValue = value as ScalarValue;
LogType arg = (LogType)scalarValue.Value;
if (arg != LogType.Default)
return false;
}
return true;
}
catch (Exception)
{
return false;
}
});
lg.WriteTo.Async(a => a.File(LogFilePath(LogType.Default.ToString()), rollingInterval: RollingInterval.Day, fileSizeLimitBytes: fileSize, rollOnFileSizeLimit: true, outputTemplate: logOutputTemplate));
})
.WriteToFilePath(new List<LogType> { LogType.Order, LogType.Goods })
.CreateLogger();
}
#endregion
/*****************************日志级别*****************************/
// FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部)
#region Info
public static void Info(string msg, params object[] args)
{
Serilog.Log.Information(msg, args);
}
public static void Info(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Information(msg, args);
}
#endregion
#region Debug
public static void Debug(string msg, params object[] args)
{
Serilog.Log.Debug(msg, args);
}
public static void Debug(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Debug(msg, args);
}
public static void Debug(Exception err, string msg)
{
Serilog.Log.Debug(err, msg);
}
public static void Debug(LogType logType, Exception err, string msg)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Debug(err, msg);
}
#endregion
#region Warning
public static void Warning(string msg, params object[] args)
{
Serilog.Log.Warning(msg, args);
}
public static void Warning(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Warning(msg, args);
}
#endregion
#region Error
public static void Error(string msg, params object[] args)
{
Serilog.Log.Error(msg, args);
}
public static void Error(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(msg, args);
}
public static void Error(Exception err, string msg)
{
Serilog.Log.Error(err, msg);
}
public static void Error(LogType logType, Exception err, string msg)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg);
}
public static void Error(Exception err, string msg, params object[] args)
{
Serilog.Log.Error(err, msg, args);
}
public static void Error(LogType logType, Exception err, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg, args);
}
#endregion
#region Fatal
public static void Fatal(string msg, params object[] args)
{
Serilog.Log.Fatal(msg, args);
}
public static void Fatal(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Fatal(msg, args);
}
public static void Fatal(Exception err, string msg)
{
Serilog.Log.Fatal(err, msg);
}
public static void Fatal(LogType logType, Exception err, string msg)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Fatal(err, msg);
}
#endregion
#region Verbose
public static void Verbose(string msg, params object[] args)
{
Serilog.Log.Verbose(msg, args);
}
public static void Verbose(Exception err, string msg)
{
Serilog.Log.Verbose(err, msg);
}
#endregion
}
自定义扩展方法,用于加载不同的日志类型
public static class LoggerConfigurationExtiensions
{
/// <summary>
/// 遍历集合项,将Log输出到对应LogType文件夹
/// </summary>
/// <param name="logger"></param>
/// <param name="logType">logType作为子Log文件夹名</param>
/// <returns></returns>
public static LoggerConfiguration WriteToFilePath(this LoggerConfiguration logger, List<LogType> logType)
{
logType.ForEach(
q =>
{
logger.WriteTo.Logger(lg =>
{
lg.Filter.ByIncludingOnly(Matching.WithProperty<LogType>(SerilogHelper.seriCustomProperty, p => p == q));
lg.WriteTo.Async(a => a.File(SerilogHelper.LogFilePath(q.ToString()), rollingInterval: RollingInterval.Day, fileSizeLimitBytes: SerilogHelper.fileSize, rollOnFileSizeLimit: true,
outputTemplate: SerilogHelper.logOutputTemplate));
});
}
);
return logger;
}
}
另外Serilog有个比较好玩的功能,扩展器Enricher,我们可以自由扩展一些属性,并把属性记录在日志中,像NLog的property一样,比如这里想把线程ID加到日志中。
//使用扩展器将线程ID附加到每个事件,可以在输出内容加入
//例:outputTemplate: "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {Message}{NewLine}{Exception}")
public class ThreadIdEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"ThreadId", Environment.CurrentManagedThreadId));
}
}
可能有人会问,为什么要定义seriCustomProperty呢,这个主要是为了兼容Microsoft.Extensions.Logging.ILogger,毕竟有些时候,我们不想去使用Helper类,而是想在Controller或者其他类中,通过依赖注入拿到ILogger对象去记录Log,比如:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
}
可是Microsoft.Extensions.Logging.ILogger也只是提供基础接口,真正的实现还是Serilog,那么如何将Microsoft.Extensions.Logging.ILogger记录的Log进入Serilog的过滤器呢,那就可以通过如下方式触发:
//{seriPos}可以理解为占位符,占位符的名称正是在SerilogHelper中声明的变量(用常量会更合适)
//string seriCustomProperty = "seriPos";
Log.Information("{seriPos}:Test", LogType.Order);
//关键代码
lg.Filter.ByIncludingOnly(e =>
{
try
{
if (e.Properties.TryGetValue(seriCustomProperty, out var value))
{
ScalarValue scalarValue = value as ScalarValue;
LogType arg = (LogType)scalarValue.Value;
if (arg != LogType.Default)
return false;
}
return true;
}
catch (Exception)
{
return false;
}
});
总之,你可以以任意方式实现,Log.Information("{seriPos}:Test", LogType.Order);这种方式虽然可以实现,但用起来也确实不太舒适,所以最终我基本不用。
后来又做了一层封装,:
public interface ISeriLogger
{
void LogVerbose(string msg, params object[] args);
void LogVerbose(LogType logType, string msg, params object[] args);
void LogInformation(string msg, params object[] args);
void LogInformation(LogType logType, string msg, params object[] args);
void LogDebug(string msg, params object[] args);
void LogDebug(LogType logType, string msg, params object[] args);
void LogDebug(Exception err, string msg);
void LogDebug(LogType logType, Exception err, string msg);
void LogWarning(string msg, params object[] args);
void LogWarning(LogType logType, string msg, params object[] args);
void LogError(string msg, params object[] args);
void LogError(LogType logType, string msg, params object[] args);
void LogError(Exception err, string msg);
void LogError(LogType logType, Exception err, string msg);
void LogError(Exception err, string msg, params object[] args);
void LogError(LogType logType, Exception err, string msg, params object[] args);
}
public class SeriLogger : ISeriLogger
{
readonly string seriCustomProperty = SerilogHelper.seriCustomProperty;
public void LogVerbose(string msg, params object[] args)
{
Serilog.Log.Verbose(msg, args);
}
public void LogVerbose(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Verbose(msg, args);
}
public void LogInformation(string msg, params object[] args)
{
Serilog.Log.Information(msg, args);
}
public void LogInformation(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Information(msg, args);
}
public void LogDebug(string msg, params object[] args)
{
Serilog.Log.Debug(msg, args);
}
public void LogDebug(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Debug(msg, args);
}
public void LogDebug(Exception err, string msg)
{
Serilog.Log.Debug(err, msg);
}
public void LogDebug(LogType logType, Exception err, string msg)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Debug(err, msg);
}
public void LogError(string msg, params object[] args)
{
Serilog.Log.Error(msg, args);
}
public void LogError(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(msg, args);
}
public void LogError(Exception err, string msg)
{
Serilog.Log.Error(err, msg);
}
public void LogError(LogType logType, Exception err, string msg)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg);
}
public void LogError(Exception err, string msg, params object[] args)
{
Serilog.Log.Error(err, msg, args);
}
public void LogError(LogType logType, Exception err, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg, args);
}
public void LogWarning(string msg, params object[] args)
{
Serilog.Log.Warning(msg, args);
}
public void LogWarning(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Warning(msg, args);
}
public void Verbose(string msg, params object[] args)
{
Serilog.Log.Verbose(msg, args);
}
public void Verbose(LogType logType, string msg, params object[] args)
{
Serilog.Log.ForContext(seriCustomProperty, logType).Verbose(msg, args);
}
}
把ISeriLogger注册到Autofac或其他容器,使用时就通过依赖注入去用即可。
总之,你可以使用SerilogHelper,也可以使用ISerilog或ILogger
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ISeriLogger seriLogger;
public HomeController(ILogger<HomeController> logger, ISeriLogger seriLogger)
{
_logger = logger;
this.seriLogger = seriLogger;
}
public IActionResult Index()
{
_logger.LogInformation("Hello world");//默认记录到Default文件夹
_logger.LogInformation("{seriPos}:Hello world",LogType.Order);//记录到Order文件夹
seriLogger.LogInformation("Hello world");//默认记录到Default文件夹
seriLogger.LogInformation(LogType.Order,"Hello world");//记录到Order文件夹
SerilogHelper.Info("Hello world");//默认记录到Default文件夹
SerilogHelper.Info(LogType.Order, "Hello world");//记录到Order文件夹
return View();
}
}
至此结束!下一篇可能是NLog