这期将NLog Git版本指向2005-06-09,NLog v0.9 released。这个时候的代码结构升级为这样:
和上期的版本相比,最明显的莫过于原先的Appender全套更名为Target。这期让我们来关注LayoutRender相关的实现。
首先LayoutRender 继承自抽象类 LayoutRenderer
,并且以属性 [LayoutRenderer("threadid")]
的方式标注。
[LayoutRenderer("threadid")] public class ThreadIDLayoutRenderer: LayoutRenderer
LayoutRendererFactory初始化的时候会根据该属性,加载指定程序集下面的所有LayoutRender。
public sealed class LayoutRendererFactory { private static TypeDictionary _targets = new TypeDictionary(); static LayoutRendererFactory() { Clear(); AddDefaultLayoutRenderers(); }
可以看到静态构造函数里面加载默认Renderers。
private static void AddDefaultLayoutRenderers() { AddLayoutRenderersFromAssembly(typeof(LayoutRendererFactory).Assembly, String.Empty); } public static void AddLayoutRenderersFromAssembly(Assembly theAssembly, string prefix) { InternalLogger.Debug("AddLayoutRenderersFromAssembly('{0}')", theAssembly.FullName); foreach (Type t in theAssembly.GetTypes()) { LayoutRendererAttribute[]attributes = (LayoutRendererAttribute[])t.GetCustomAttributes(typeof(LayoutRendererAttribute), false); if (attributes != null) { foreach (LayoutRendererAttribute attr in attributes) { AddLayoutRenderer(prefix + attr.FormatString, t); } } } }
下一步就是解析配置文件中的Layout,一般包含Layout 的配置文件是这样子的:
<nlog> <targets><target name='debug' type='Debug' layout='${basedir} ${message}' /></targets> <rules> <logger name='*' minlevel='Debug' appendTo='debug' /> </rules> </nlog>
这里的layout字符串是: ${basedir} ${message}
,我们希望得到两个Render的数组。
这里 layout.cs
里面有个关键的方法
private static LayoutRenderer[] CompileLayout(string s, out int needsStackTrace) { ArrayList result = new ArrayList(); needsStackTrace = 0; int startingPos = 0; int pos = s.IndexOf("${", startingPos); while (pos >= 0) { if (pos != startingPos) { result.Add(new LiteralLayoutRenderer(s.Substring(startingPos, pos - startingPos))); } int pos2 = s.IndexOf("}", pos + 2); if (pos2 >= 0) { startingPos = pos2 + 1; string item = s.Substring(pos + 2, pos2 - pos - 2); int paramPos = item.IndexOf(':'); string LayoutRenderer = item; string LayoutRendererParams = null; if (paramPos >= 0) { LayoutRendererParams = LayoutRenderer.Substring(paramPos + 1); LayoutRenderer = LayoutRenderer.Substring(0, paramPos); } LayoutRenderer newLayoutRenderer = LayoutRendererFactory.CreateLayoutRenderer(LayoutRenderer, LayoutRendererParams);
在这个版本中,采取的是简单粗暴的截取字符串的方式。当需要的 Render
不存在的时候,创建的是 LiteralLayoutRenderer
。当存在的时候,存放在数组里返回。
LiteralLayoutRenderer
本身不做什么格式化输出,只是简单的返回当前模板的值。
protected internal override int GetEstimatedBufferSize(LogEventInfo ev) { return _txt.Length; } /// <summary> /// Renders the specified string literal and appends it to the specified <see cref="StringBuilder" />. /// </summary> /// <param name="builder">The <see cref="StringBuilder"/> to append the rendered data to.</param> /// <param name="ev">Logging event.</param> protected internal override void Append(StringBuilder builder, LogEventInfo ev) { builder.Append(_txt); }
最后就是Render的输出了。相比于第一思维的直接 string.Replace
,走了一圈 Render
的方式更加的安全。
StringBuilder builder = new StringBuilder(size); for (int i = 0; i < _LayoutRenderers.Length; ++i) { LayoutRenderer app = _LayoutRenderers[i]; try { app.Append(builder, ev); } catch (Exception ex) { if (InternalLogger.IsWarnEnabled) { InternalLogger.Warn("Exception in {0}.Append(): {1}.", app.GetType().FullName, ex); } } } return builder.ToString();
这里有一个比较小的注意点,就是StringBuilder初始化的时候指定一个预估的容量 size
,可以较少性能损失。