我们将版本向前切换到20051025,这期的关注点是filter。我们在使用日志的时候可能希望加上一些过滤器,在满足某些特定条件的时候才输出。举个简单的使用方式如下:
<nlog>
<targets><target name='debug' type='Debug' layout='${basedir} ${message}' /></targets>
<rules>
<logger name='*' minlevel='Debug' appendTo='debug'>
<filters>
<whenContains layout='${message}' substring='zzz' action='Ignore' />
</filters>
</logger>
</rules>
</nlog>
FilterFactory负责初始化程序集中的Filter
public sealed class FilterFactory
{
private static TypeDictionary _filters = new TypeDictionary();
static FilterFactory()
{
foreach (Assembly a in ExtensionUtils.GetExtensionAssemblies())
{
AddFiltersFromAssembly(a, "");
}
}
反射初始化的方式和上篇的LayoutRender没有差别
public static void AddFiltersFromAssembly(Assembly theAssembly, string prefix)
{
try
{
InternalLogger.Debug("AddFiltersFromAssembly('{0}')", theAssembly.FullName);
foreach (Type t in theAssembly.GetTypes())
{
FilterAttribute[]attributes = (FilterAttribute[])t.GetCustomAttributes(typeof(FilterAttribute), false);
if (attributes != null)
{
foreach (FilterAttribute attr in attributes)
{
AddFilter(prefix + attr.Name, t);
}
}
}
}
catch (Exception ex)
{
InternalLogger.Error("Failed to add filters from '" + theAssembly.FullName + "': {0}", ex);
}
}
只是简单的从初始化好的_filters字典里面取出即可,如果发现没有,就会尝试反射创建
还是在LoggerImpl
的Write
方法中以链式的方式嵌入
for (TargetWithFilterChain awf = targets; awf != null; awf = awf.Next)
{
Target app = awf.Target;
try
{
FilterCollection filterChain = awf.FilterChain;
FilterResult result = FilterResult.Neutral;
for (int i = 0; i < filterChain.Count; ++i)
{
Filter f = filterChain[i];
result = f.Check(logMessage);
if (result != FilterResult.Neutral)
break;
}
if (result == FilterResult.Ignore)
{
if (InternalLogger.IsDebugEnabled)
{
InternalLogger.Debug("{0}.{1} Rejecting message because of a filter.", logger.Name, level);
}
continue;
}
}
这里可以看到,如果filter 的result 是Ignore
,该Message的Target就不需要再输出了。
TargetWithFilterChain
是包装Target的关键类型:
internal class TargetWithFilterChain
{
private Target _target;
private FilterCollection _filterChain;
private TargetWithFilterChain _next;
public TargetWithFilterChain(Target a, FilterCollection filterChain)
{
_target = a;
_filterChain = filterChain;
}
这个类型的实现很简单,就是将Target和FilterChain打包放在一起。
这里值得注意的细节是InternalLogger
记录了内部一些详情,当我们需要的时候直接开启InternalLogger
调试日志组件的工作过程。
我们对于InternalLogger
的要求是不要影响正常程序的运行过程,仅仅在调试日志组件功能的时候使用,因此里面的代码牺牲异常也保证安全。
static InternalLogger()
{
try
{
switch (ConfigurationSettings.AppSettings["nlog.internalLogToConsole"].ToLower())
{
case "false":
LogToConsole = false;
break;
case "true":
LogToConsole = true;
break;
default:
if (EnvironmentHelper.GetSafeEnvironmentVariable("NLOG_INTERNAL_LOG_TO_CONSOLE") != null)
{
LogToConsole = true;
}
break;
}
string levelString = ConfigurationSettings.AppSettings["nlog.internalLogLevel"];
if (levelString == null || levelString.Length == 0)
levelString = EnvironmentHelper.GetSafeEnvironmentVariable("NLOG_INTERNAL_LOG_LEVEL");
if (levelString != null && levelString.Length > 0)
LogLevel = LogLevel.FromString(EnvironmentHelper.GetSafeEnvironmentVariable("NLOG_INTERNAL_LOG_LEVEL"));
LogFile = ConfigurationSettings.AppSettings["nlog.internalLogFile"];
if (LogFile == null)
LogFile = EnvironmentHelper.GetSafeEnvironmentVariable("NLOG_INTERNAL_LOG_FILE");
Info("NLog internal logger initialized.");
}
catch {}
}
可以看到InternalLogger
的开启不依赖于原来的配置文件结构,默认不输出,以及取环境变量处理的也非常小心。
public static string GetSafeEnvironmentVariable(string name)
{
try
{
string s = Environment.GetEnvironmentVariable(name);
if (s == "")
return null;
else
return s;
}
catch (SecurityException)
{
return "";
}
}