利用log4net创建日志文件时过滤日志,这是坑还是?
前言
网上貌似没有太多关于log4net过滤日志的资料,在研究过程中发现一点小问题,这里做下记录,希望对后续有用到的童鞋起到一丢丢帮助作用。
log4net日志过滤
由于是在.NET Core中使用,所以这里为了演示,我们创建一个.NET Core控制台程序,同时呢通过安装log4net最新稳定版本(2.0.8),好了,对于.NET Core而言,在开发时可直接配置web.config启用日志功能,此时会将不同级别日志直接放在同一文件中,在实际开发中无论我们使用log4net还是serilog或者自己写一个也好,大部分都会根据不同级别创建不同目录,这样更加易于后续跟踪和排查问题。但是也会存在特殊的需求,比如本文中,我们只需要创建两个日志文件,一个用于正常的信息文件记录,一个是审计信息记录,而且日志文件名可能不是日期格式,而是由我们自己根据配置给定,所以基于以上需求,我们来完成此项任务,首先,我们在控制台根目录下创建如下单独的log4net .config配置文件:
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="log.txt" /> <appendToFile value="true" /> <countDirection value="1"/> <maximumFileSize value="10MB" /> <maxSizeRollBackups value="-1" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> <root> <level value="DEBUG"/> <appender-ref ref="RollingFileAppender" /> </root> </log4net>
接下来则是读取上述配置文件,在.NET Framework中我们需要在Properties文件夹下的AssemblyInfo类中添加读取上述日志配置文件类,如下:
[assembly: log4net.Config.XmlConfigurator(ConfigFile ="log4net.config", Watch = true)]
但是到了.NET Core中压根就没有了上述Properties文件夹,此时我能想到的办法只能根据日志配置文件所在的目录去读取(不知是否还有其他更好的办法),并按照所提供的api,创建控制台程序集仓储和读取配置文件中XML的根节点并最终写到log4net配置中,如下:
var log4netFullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\")); var log4netConfig = new XmlDocument(); log4netConfig.Load(File.OpenRead(Path.Combine(log4netFullPath, "log4net.config"))); var repo = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Repository.Hierarchy.Hierarchy)); Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);
接下来我们则是创建类的日志接口,测试并打印日志,如下:
class Program { static readonly ILog log = LogManager.GetLogger(typeof(Program)); static void Main(string[] args) { var log4netFullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\")); var log4netConfig = new XmlDocument(); log4netConfig.Load(File.OpenRead(Path.Combine(log4netFullPath, "log4net.config"))); var repo = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Repository.Hierarchy.Hierarchy)); Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]); log.Info("Program start success......"); } }
在log4net中,通过添加节点PropertyFilter来实现属性过滤,如下:
<filter type="log4net.Filter.PropertyFilter"> <key value="" /> <stringToMatch value="" /> <acceptOnMatch value="" /> </filter>
如上key代表需要我们定义的键,而stringToMatch代表我们通过对应键所添加的值,最后acceptOnMatch代表是否接受匹配,为布尔值,默认为true(接收匹配就记录)。若我们在配置文件appender节点小添加如下节点:
<filter type="log4net.Filter.PropertyFilter"> <key value="filter" /> <stringToMatch value="1" /> <acceptOnMatch value="false" /> </filter>
如上设置代表,当我们对应设置属性的键为filter,而值为1时则记录日志,接下来我们在上述控制台基础通过log4net提供的api去设置线程级别的属性,添加如下:
ThreadContext.Properties["filter"] = "1"; log.Info("1......"); ThreadContext.Properties["filter"] = "0"; log.Info("0......");
我们通过设置键filter的值为1和0时都记录下来了,很显然没达到要求,我们只想记录对应值等于1的日志,此时需要在上述节点继续添加如下一行来只记录此属性键对应值的日志:
好了,到此有了对log4net日志过滤的基础铺垫,接下来实现我们的需求:创建两个日志文件,一个记录正常信息(排除审计),一个只记录审计信息 。接下来我们对配置文件修改如下:
<?xml version="1.0" encoding="utf-8" ?> <log4net> <!--只接收auditing日志--> <appender name="AuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="auditing.txt" /> <appendToFile value="true" /> <countDirection value="1"/> <maximumFileSize value="10MB" /> <maxSizeRollBackups value="-1" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> <filter type="log4net.Filter.PropertyFilter"> <key value="filter" /> <stringToMatch value="auditing" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> </appender> <!--只接收除auditing以外的日志--> <appender name="ExceptAuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="non-auditing.txt" /> <appendToFile value="true" /> <countDirection value="1"/> <maximumFileSize value="10MB" /> <maxSizeRollBackups value="-1" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> <filter type="log4net.Filter.PropertyFilter"> <key value="filter" /> <stringToMatch value="auditing" /> <acceptOnMatch value="false" /> </filter> </appender> <root> <level value="DEBUG"/> <appender-ref ref="AuditingRollingFileAppender" /> <appender-ref ref="ExceptAuditingRollingFileAppender" /> </root> </log4net>
上述配置文件就不用我多讲,接下来我们将控制台中上述测试打印的日志给移除,我们添加如下代码进行测试:
//只接收auditing日志 ThreadContext.Properties["filter"] = "auditing"; log.Info("auditing......."); //只接收除了auditing以外日志 ThreadContext.Properties["filter"] = "non-auditing"; log.Info("non-auditing......."); log.Warn("测试非审计......"); //只接收auditing日志 ThreadContext.Properties["filter"] = "auditing"; log.Info("测试审计.......");
虽然创建一个审计日志文件和一个非审计日志文件,我们的配置也没任何毛病,但是通过上述日志输出发现,非审计文件压根没有日志却全部到了审计文件里,这是为何呢? 经过排查,我开始猜测log4net难道是对值进行模糊匹配吗?为了验证猜想,我将上述对非审计的值(多加一个字母Z)修改成如下:
只要我们将上述非审计日志的值设置时并不包含审计日志里的值(auditing)就没问题,真香,哈哈。上述排除auditing节点以外日志的节点是从反向考虑,当然我们也可以从正向考虑,设置为其他值,还是注意不要包含auditing,并且只记录该值的记录,如下:
<!--只接收除auditing以外的日志--> <appender name="ExceptAuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="non-auditing.txt" /> <appendToFile value="true" /> <countDirection value="1"/> <maximumFileSize value="10MB" /> <maxSizeRollBackups value="-1" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> <filter type="log4net.Filter.PropertyFilter"> <key value="filter" /> <stringToMatch value="other" /> <acceptOnMatch value="true" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> </appender>
在log4net中除了设置属性过滤外,还可以比如通过日志中的字符串进行匹配,比如我们添加如下字符串匹配节点,说明包含cache_log的值将不会被记录:
<filter type="log4net.Filter.StringMatchFilter"> <stringToMatch value="cache_log" /> <acceptOnMatch value="false" /> </filter>
log4net运行时创建日志
我们可能会遇到根据什么规则或者需要在运行时创建日志文件,这个时候就不能如上写死日志文件名了,我们通过log4net提供给我们的api【%property】来实现,我们将上述节点file进行如下修改:
<file type="log4net.Util.PatternString" value="D:\logs\%property{LogName}" />
接着添加创建日志文件名代码,注意要将如下第一行放在第二行前面,否则创建的文件名将为null,如下:
ThreadContext.Properties["LogName"] = "runtime.log"; Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);
总结
log4net在以前.NET Framework中用到的比较多,但是并未使用过过滤日志这一特性,最近要使用时研究发现的问题,看到网上对此资料甚少,所以在此作为备忘录,能够帮到有需要的童鞋当然再好不过啦。