Use Logging Application Block 6.0



写日志主要是通过LogWriter类来完成的,如下所示:

LoggingConfiguration config = new LoggingConfiguration();

LogWriter writer = new LogWriter(config);           

writer.Write("Hello world.");

执行这几行代码,不会出现异常,但也没写入任何日志。因为我们还没有设置config对象。必须配置LogSource才能将写入日志,因为LogSource包含了TraceListener信息,而TraceListener包含了将日志信息写入到具体存储设备或服务的实现。

配置LogSource非常简单,如下所示:

LoggingConfiguration config = new LoggingConfiguration();         

config.AddLogSource("MyLogSource", SourceLevels.All, true,

    new TextWriterTraceListener("Logs/MyApp.log"));

LogWriter writer = new LogWriter(config);           

writer.Write("Hello world.");

上面的代码添加了一个LogSource,并为LogSource指定了相关信息,这些信息对应到LogSource类的4个属性,如下:

  • public bool AutoFlush { get; set; }

    自动刷新日志,这表示LogSource会在写入日志的时候自动调用每个ListenerFlush方法;否则必须要手工调用Flush方法才能将日志写入到存储设备。默认值为true

  • public SourceLevels Level { get; set; }

    此属性表示只有高于Level指定的严重性的日志才会被该LogSource接收。具体用法可见SourceLevels枚举类型的定义。如果只想写入错误日志,那么此属性会非常有用。

  • public IEnumerable<TraceListener> Listeners { get; }

    一个LogSource可以包含多个TraceListener,这表示同一条日志信息可以写入到多个设备。一般情况下,都不会多次写同一份日志到不同的存储中。所以大部分LogSource只包含一个TraceListener

  • public string Name { get; }

    LogSource的名字,便于在配置文件中引用。

通过添加LogSource,并在TextWriterTraceListener的构造函数中指定文件路径就可以将日志写入到日志文件中,如果是第一次写入日志,则自动创建目录和文件,否则将已追加的方式写入日志。

Enterprise Library中,我们创建的LogSource被称为目录LogSource,日志源的名字被称为目录名。可以这样理解,我们创建的LogSource其实就是为日志信息做了一个分类而已。例如,我们的应用里包含多个模块,每个模块都可以定义一个LogSource,但是都使用同一个TraceListener写入到同一个文件,这样我们可以在这个文件里面看到每条日志属于哪个LogSource。除了我们创建的目录LogSource,还有三个特殊的LogSource,它们由LoggingConfiguration. SpecialSources属性指定,该属性的类型是SpecialSourcesConfiguration,它包括三个属性,指定了三个特殊的日志源,我们可以对它们分别配置,三个属性的具体解释如下:

  • public SpecialLogSourceData AllEvents { get; }

    如果为AllEvents配置了Listener,那么所有的日志都会写入到指定的Listener。大部分情况下,都不应该使用此LogSource

  • public SpecialLogSourceData LoggingErrorsAndWarnings { get; }

    如果写日志的时候错误,那么日志将被写入到此LogSource。日志错误永远也不应该影响正常的业务流程。此LogSource用于检测日志系统本身的问题。

  • public SpecialLogSourceData Unprocessed { get; }

    指定了Category的日志项,如果没有被指定的LogSource处理,那么将会被此属性代表的LogSource处理。此LogSource用于检测日志系统的配置问题或者写日志项时使用了错误的Category名称。

在上面的示例中,需要注意的一个问题是,我们指定了categoryMyLogSource,但是在写日志的时候,并没有指定category。理论上应该是使用如下代码将日志写入名为MyLogSourceLogSource

writer.Write("Hello world.", "MyLogSource");

但是我们没有这么做也取得了同样的结果,原因就在于config对象的DefaultSource 属性,此属性值被指定为第一个LogSource的名字,所以默认情况下,我们向第一个LogSource写入日志。应该尽量明确指定category,而不要使用默认值。

下面的代码设定LogSource只写入Error级别或者更高级别的日志。可以通过此属性设置自己想要的日志信息,而不需要输出所有日志信息。

config.AddLogSource(

    "MyLogSource",

    SourceLevels.Error,

    true,

    new TextWriterTraceListener("Logs/MyApp.log"));

 

LogWriter writer = new LogWriter(config);

writer.Write("Hello world.", "MyLogSource", 0, 1, TraceEventType.Error);

如果把写日志时的Error改为Information,那么日志项将不会被写入。可以看到SourceLevels枚举类型其实是对TraceEventType枚举类型的重新定义,具体的映射关系在帮助文档中有明确说明。

一个系统的日志可能非常多,如果能够配置只输出我们希望看到的日志,有利于帮助我们分析问题。LogFilter类就是用来解决此问题的。下面的代码演示了如何使用CategoryFilter来过滤指定目录的日志信息:

LoggingConfiguration config = new LoggingConfiguration();

var listener = new TextWriterTraceListener("Logs/MyApp.log");

config.AddLogSource("MyLogSource", SourceLevels.All, true,listener);

config.AddLogSource("AnotherLogSource", SourceLevels.All, true, listener);

var filter = new CategoryFilter(

    "MyFilter",

    new [] { "MyLogSource"},

    CategoryFilterMode.DenyAllExceptAllowed);

config.Filters.Add(filter);

LogWriter writer = new LogWriter(config);

writer.Write("Hello world.", "MyLogSource");

writer.Write("Hello Again.", "AnotherLogSource");

CategoryFilter的构造函数指定了只写入到名称为MyLogSourceLogSource中。最后的枚举类型参数指定了如何对待指定的LogSource。详情可参考MSDN

如果服务应用有着很高的并发,那么写日志操作很有可能会影响性能,为了降低日志操作对性能的影响,我们可以对TraceListener进行异步包装,如下所示:

config.AddLogSource("MyLogSource", SourceLevels.All, true)

               .AddAsynchronousTraceListener(listener, 30000, null, null);

可以看到AddAsynchronousTraceListener方法需要的四个参数,第一个不用多少,后三个需要小心设置。

  • int? bufferSize

    日志请求的缓冲区大小,默认值是30000,也就是说如果队列里有30000个日志请求还没有写入,那么下一个请求,将会被拒绝。应根据服务器内存大小合理设置此值。

  • int? maxDegreeOfParallelism

    最大的并发数,如果TraceListener是线程安全的,那么将启用并发写入,并发数默认为处理器个数。尽量不要修改此数值。

  • TimeSpan? disposeTimeout

    如果进程被关闭,那么队列里的日志将被尽量写入,如果超过了此时间设置,那么没有被写入的日志将会丢失。默认值为System.Threading.Timeout.InfiniteTimeSpan,也就是说,进程需要一直等待所有的日志被写入完毕。

LogWriter允许运行时改变配置,而不用重启进程,如下所示:

writer.Configure(logConfig =>{  //Change your logging settings.  });

 

如果要使用配置文件来配置日志,需要使用如下代码创建LogWriter

LogWriterFactory fac = new LogWriterFactory();

LogWriter writer = fac.Create();

writer.Write("Hello world.");

建议使用企业库提供的EntLibConfig.exe来编辑配置文件,可视化界面使得编辑工作更简单有效。

 

追踪关联活动,如果把多个日志项通过某个属性关联到一起,能极大的帮助我们分析某一次调用的流程。幸运的是,企业库提供了这样的功能。如下所示:

TraceManager mgr = new TraceManager(writer);

using (var trace =mgr.StartTrace("SaveUser"))

{

    writer.Write("Begin save user in service layer");

    writer.Write("save user to database");

}

使用TraceManager类的StartTrace方法,启动一次活动跟踪。在using块内部执行的写日志操作都作为此次活动的一部分。如果写日志操作没有指定category,那么日志项将的category被设置为StartTrace方法的第一个参数指定的值。否则日志项将同时具有其自己的category和参数值指定的category。上面的示例中,虽然我们只写了两条日志,实际上将会多输出额外的两条日志,这两条日志是StartTrace方法返回的Tracer对象写入的,它在构造函数和Dispose方法中分别写入一条活动开始日志和活动结束日志。最重要的是,在结束日志中包含了方法的执行时间,由此,我们可以写一个工具,分析方法的性能。因为这两条额外的日志只属于StartTrace方法指定的category,所示我们需要用AllLogSource日志源来收集这些日志记录。StartTrace方法还可以指定第二个参数,表示活动ID,此参数最终将被赋予LogEntryActivityId属性。如果我们没有指定这个值,那么Tracer对象将会为我们指定一个唯一的GUID。一般情况下,我们应该总是这么做,因为自己指定这个Id好像也没什么意义。为了在输出中显示ActivityID,需要设置日志的Formattertemplate属性包含如下字符串:

ActivityId:{property(ActivityId)}{newline}&#xA;

活动跟踪甚至可以在多线程中使用,如下:

TraceManager mgr = new TraceManager(writer);

var tracer = mgr.StartTrace("SaveUser");

writer.Write("Begin save user");

 

ThreadPool.QueueUserWorkItem(o =>

   {

        writer.Write("save user in another thread");

    });

trace.Dispose();

但是一定要注意的是,必须在同一个线程中调用StartTracetrace.Dispose方法。否则线程存储上的操作栈会处于不正确的状态。而且上面的在另外一个线程中写入的日志不是循序写入的,也就是说日志有可能在活动结束的日志项后写入。

Tracer类支持嵌套使用,如果没有为内部的Tracer指定活动ID,那么内部Tracer将会继承外部Tracer的活动ID

 

总结:

  1. 使用LogWriterFactory类来创建LogWriter,从而可以使用配置文件来配置日志状态。

  2. 使用LogWriter来写日志,最重要的几个方法如下:

    public LogWriter(LoggingConfiguration config);

    此构造函数需要使用一个LoggingConfiguration对象来对日志系统进行配置。

    public void Configure(Action<LoggingConfiguration> configurationScript);

    使用此方法在不用重启进程的情况下重新配置LogWriter

    public bool IsLoggingEnabled();

    此方法用于检测日志是否打开,此选项在LoggingConfiguration中进行设置。写日志前做此检测可以避免对日志方法压栈,从而提高性能。此方法根据LogEnabledFilter的状态返回结果。

    public bool IsTracingEnabled();

    此方法用于检测Tracing是否启用,此选项在LoggingConfiguration中进行设置。如果Tracing被禁用,那么活动跟踪将不会被写入到日志文件。

    public bool ShouldLog(LogEntry log);

    次方法比IsLoggingEnabled检测更为全面,它检查所有的Filter,以确定日志项是否通过过滤器。

    public void Write(LogEntry log);

    此方法写入日志,还有很多重载方法,但最终都会调用此方法。

    public void SetContextItem(object key, object value);

    此方法用于在线程的逻辑上下文中添加数据,所有在此线程上写入的日志都将包含你所设置的keyvalue信息。此信息被添加到LogEntryExtendedProperties字典中并最终写入日志文件。一个有用的信息比如当前用户名可以用过此方法写入到日志中。

    public voidFlushContextItems();

    此方法用于清除SetContextItem方法设置的所有数据。

    public void Dispose();

    最后要注意的是LogWriter实现了IDisposable接口;应该在应用程序关闭的时候释放LogWriter。一般情况下,应该是不需要手动调用Dispose方法的,但是假如使用了异步的TraceListener,不调用Dispose会造成日志丢失。

  3. 企业库还提供了Logger类,它作为LogWriterFaçade封装,使用静态方法来操作日志。

  4. LoggingConfiguration类提供了所有的日志配置,其中的有些属性与LogWriter相同,不再重复解释,几个重要的属性和方法解释如下:

    public string DefaultSource { get; set; }

    没有指定category的日志项将会写入到DefaultSource指定的LogSource

    public LogSourceDataCollection LogSources { get; }

    获取所有的LogSourceData集合(不包括特殊的LogSourceData)。

    public SpecialSourcesConfiguration SpecialSources { get; }

    获取一个配置,通过此配置可以配置特殊LogSource。特殊LogSource前面有解释,不再赘述。

    public LogSourceData AddLogSource(string name, SourceLevels level, bool autoFlush, params TraceListener[] traceListeners);

    添加一个LogSource,指定LogSource的名称(category),最低通过级别,自动刷新和一个监听器集合。

    public IList< ILogFilter> Filters { get; }

    获取或者设置日志过滤器,通过LogFilter可以控制日志项是否应该被输出到存储。企业库支持三种过滤器,它们是:

  • CategoryFilter,按照LogSource的名称过滤。

  • LogEnabledFilter,是否启用日志。

  • PriorityFilter,按照LogEntryPriority属性过滤。

  1. 所有的日志最终都是通过TraceListener写入到存储设备中,因为存储类型种类繁多,所以会有很多派生自TraceListener的实现,在此我们只列出比较重要的可实例化的几个派生类,如下所示:

  • EmailTraceListener,将日志作为邮件发送。

  • FormattedEventLogTraceListener,写入Windows日志系统。

  • MsmqTraceListener,发送到MSMQ

  • RollingFlatFileTraceListener,写入到一个文本文件。

  • XmlTraceListener,写入到一个文本文件,按照XML格式写入。

  • FormattedDatabaseTraceListener,写入到数据库。

    这里只对RollingFlatFileTraceListener做一个详细解释,RollingFlatFileTraceListener可以设置一个roll size,这样当日志文件大小超过此设置值后,RollingFlatFileTraceListener就会创建一个新的日志文件,以防止创建巨大的日志文件。还可以设置总日志文件数,控制总日志大小,防止日志占据整个磁盘。通过解释其构造函数参数,就能使我们对其进行一个很好的了解:

  • string fileName, 日志文件名。可以使用绝对路径或相对路径。

  • string header,分割日志项的头。

  • string footer,分割日志项的脚

  • ILogFormatter formatter,用于格式化日志,格式化后才可写入。

  • int rollSizeKB,每个日志文件的最大空间。超过此大小,创建新的日志文件。

  • string timeStampPattern,日志中时间戳的格式。

  • RollFileExistsBehavior rollFileExistsBehavior,指定如果要创建的日志文件已经存在,要采取的措施。Overwrite表示覆盖, Increment表示会继续寻找一个日志目录中不存在的文件名,用此文件名创建新日志文件。

  • RollInterval rollInterval,指定了检查是否要roll日志文件的时间间隔。

  • int maxArchivedFiles,最大日志文件数。超过此数目,最老的日志文件将被删除。

  • public TraceOptions TraceOutputOptions { get; set; },此属性由基类定义,可以控制有哪些信息可以输出到日志文件。

  1. 通过 ILogFormatter控制日志格式,上面我们看到TraceListener需要一个formatter来控制日志格式,企业库提供了四种日志格式化器,如下:

  • BinaryLogFormatter,格式化为二进制。

  • JsonLogFormatter,格式化为Json字符串。

  • TextFormatter,格式化为纯文本。

  • XmlLogFormatter,格式化为XML格式。

    我们只对TextFormatter做进一步解释,TextFormatter.Template属性控制了格式化信息,这是一个字符串,其中有很多占位符。要记住这些占位符的名字很困难,但是可以使用日志配置编辑工具可以看到所有的占位符,它们大多都对应于LogEntry的属性。

     

     

你可能感兴趣的:(Use Logging Application Block 6.0)