对比log4net,EntLib 的可视化配置以及完善的文档实在是占了挺大的优势,但两者在文件日志方面都存在着相同的问题,就是不能根据Category(log4net里面是logger name)自动分类存放(所有的日志都记在一个日志文件里,当查看日志时会相对比较麻烦),如果想实现分类存放,那就需要在config文件里写上一大堆的配置,作为懒人,这肯定是不能接受的,当然是写的越少越好:P
在code之前先描述下设计思路:
首先说下前提,EntLib 的Logging Application Block支持SpecialSources,它包含三部分AllEvents、LoggingErrorsAndWarnings以及Unprocessed,这里面的Unprocessed对应的就是Unprocessed Category,所有未定义过的Categories都会被分配到此部分,如果Unprocessed没设置TraceListener,那就会产生一个异常,由LoggingErrorsAndWarnings来处理这个异常
其次是思路,说白了很简单,就是根据Category动态创建新的RollingFlatFileTraceListener,然后在记录日志时,由Category对应的RollingFlatFileTraceListener来处理相应的日志,如果无法识别的日志,则由默认的RollingFlatFileTraceListener来处理
PS:微软的企业库配置小软件无法通过加载dll识别该类,估计是因为AutoCategoryRollingFlatFileTraceListenerData直接继承自TraceListenerData,而不是继承自CustomTraceListenerData,导致小工具无法识别,不过可以将配置手写到config文件中,此时是能够正确加载的
以下是具体code
using System.IO; using System.Collections.Concurrent; using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners; using System.Diagnostics; using Microsoft.Practices.EnterpriseLibrary.Logging; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration; using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters; using Microsoft.Practices.EnterpriseLibrary.Common.Utility; using System.Configuration; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Design; #region AutoCategoryRollingFlatFileTraceListener /// <summary> /// 根据Category自动分类保存日志 /// </summary> [ConfigurationElementType(typeof(AutoCategoryRollingFlatFileTraceListenerData))] public class AutoCategoryRollingFlatFileTraceListener : CustomTraceListener { private ConcurrentDictionary<string, RollingFlatFileTraceListener> _dic = new ConcurrentDictionary<string, RollingFlatFileTraceListener>(); private RollingFlatFileTraceListener _defaultListener; private readonly string _directory; private readonly string _extension; private readonly string _header; private readonly string _footer; private readonly int _rollSizeKB; private readonly string _timeStampPattern; private readonly RollFileExistsBehavior _rollFileExistsBehavior; private readonly RollInterval _rollInterval; private readonly int _maxArchivedFiles; /// <summary> /// Initializes a new instance of the <see cref="RollingFlatFileTraceListener"/> class. /// </summary> /// <param name="fileName">The filename where the entries will be logged.</param> /// <param name="header">The header to add before logging an entry.</param> /// <param name="footer">The footer to add after logging an entry.</param> /// <param name="formatter">The formatter.</param> /// <param name="rollSizeKB">The maxium file size (KB) before rolling.</param> /// <param name="timeStampPattern">The date format that will be appended to the new roll file.</param> /// <param name="rollFileExistsBehavior">Expected behavior that will be used when the roll file has to be created.</param> /// <param name="rollInterval">The time interval that makes the file rolles.</param> /// <param name="maxArchivedFiles">The maximum number of archived files to keep.</param> public AutoCategoryRollingFlatFileTraceListener(string fileName, string header = RollingFlatFileTraceListener.DefaultSeparator, string footer = RollingFlatFileTraceListener.DefaultSeparator, ILogFormatter formatter = null, int rollSizeKB = 0, string timeStampPattern = "yyyy-MM-dd", RollFileExistsBehavior rollFileExistsBehavior = RollFileExistsBehavior.Overwrite, RollInterval rollInterval = RollInterval.None, int maxArchivedFiles = 0) { Guard.ArgumentNotNullOrEmpty(fileName, "fileName"); this._directory = Path.GetDirectoryName(fileName); this._extension = Path.GetExtension(fileName); this._header = header; this._footer = footer; this._rollSizeKB = rollSizeKB; this._timeStampPattern = timeStampPattern; this._rollFileExistsBehavior = rollFileExistsBehavior; this._rollInterval = rollInterval; this._maxArchivedFiles = maxArchivedFiles; this.Formatter = formatter; this._defaultListener = new RollingFlatFileTraceListener(fileName, this._header, this._footer, this.Formatter, this._rollSizeKB, this._timeStampPattern, this._rollFileExistsBehavior, this._rollInterval, this._maxArchivedFiles); } public override void Write(string message) { this._defaultListener.Write(message); } public override void WriteLine(string message) { this._defaultListener.WriteLine(message); } public override void Flush() { this._defaultListener.Flush(); } /// <summary> /// Delivers the trace data to the underlying file. /// </summary> /// <param name="eventCache">The context information provided by <see cref="System.Diagnostics"/>.</param> /// <param name="source">The name of the trace source that delivered the trace data.</param> /// <param name="eventType">The type of event.</param> /// <param name="id">The id of the event.</param> /// <param name="data">The data to trace.</param> public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) { if (this.Filter == null || this.Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null)) { var listener = this._defaultListener; if (data is LogEntry) { ((LogEntry)data).Categories.ForEach((category) => { var tmpListener = this.GetTraceListener(category); tmpListener.TraceData(eventCache, source, eventType, id, data); tmpListener.Flush(); }); return; } listener.TraceData(eventCache, source, eventType, id, data); listener.Flush(); } } private RollingFlatFileTraceListener GetTraceListener(string category) { RollingFlatFileTraceListener listener = this._defaultListener; if (!string.IsNullOrWhiteSpace(category)) { category = category.ToLower().Trim(); if (this._dic.ContainsKey(category)) { return this._dic[category]; } else { try { string fileName = Path.Combine(this._directory, category, string.Format("{0}{1}", category, this._extension)); var tmpListener = new RollingFlatFileTraceListener(fileName, this._header, this._footer, this.Formatter, this._rollSizeKB, this._timeStampPattern, this._rollFileExistsBehavior, this._rollInterval, this._maxArchivedFiles); this._dic.TryAdd(category, tmpListener); listener = tmpListener; } catch { } } } return listener; } } #endregion #region AutoCategoryRollingFlatFileTraceListenerData public class AutoCategoryRollingFlatFileTraceListenerData : TraceListenerData { private const string FileNamePropertyName = "fileName"; private const string footerProperty = "footer"; private const string formatterNameProperty = "formatter"; private const string headerProperty = "header"; private const string RollFileExistsBehaviorPropertyName = "rollFileExistsBehavior"; private const string RollIntervalPropertyName = "rollInterval"; private const string RollSizeKBPropertyName = "rollSizeKB"; private const string TimeStampPatternPropertyName = "timeStampPattern"; private const string MaxArchivedFilesPropertyName = "maxArchivedFiles"; public AutoCategoryRollingFlatFileTraceListenerData() : base(typeof(AutoCategoryRollingFlatFileTraceListener)) { ListenerDataType = typeof(AutoCategoryRollingFlatFileTraceListenerData); } public AutoCategoryRollingFlatFileTraceListenerData(string name, string fileName, string header, string footer, int rollSizeKB, string timeStampPattern, RollFileExistsBehavior rollFileExistsBehavior, RollInterval rollInterval, TraceOptions traceOutputOptions, string formatter) : base(name, typeof(AutoCategoryRollingFlatFileTraceListener), traceOutputOptions) { FileName = fileName; Header = header; Footer = footer; RollSizeKB = rollSizeKB; RollFileExistsBehavior = rollFileExistsBehavior; RollInterval = rollInterval; TimeStampPattern = timeStampPattern; Formatter = formatter; } public AutoCategoryRollingFlatFileTraceListenerData(string name, string fileName, string header, string footer, int rollSizeKB, string timeStampPattern, RollFileExistsBehavior rollFileExistsBehavior, RollInterval rollInterval, TraceOptions traceOutputOptions, string formatter, SourceLevels filter) : base(name, typeof(AutoCategoryRollingFlatFileTraceListener), traceOutputOptions, filter) { FileName = fileName; Header = header; Footer = footer; RollSizeKB = rollSizeKB; RollFileExistsBehavior = rollFileExistsBehavior; RollInterval = rollInterval; TimeStampPattern = timeStampPattern; Formatter = formatter; } /// <summary> /// FileName /// </summary> [ConfigurationProperty(FileNamePropertyName, DefaultValue = "auto.log")] [System.ComponentModel.Editor(CommonDesignTime.EditorTypes.FilteredFilePath, CommonDesignTime.EditorTypes.UITypeEditor)] public string FileName { get { return (string)this[FileNamePropertyName]; } set { this[FileNamePropertyName] = value; } } /// <summary> /// Gets and sets the footer. /// </summary> [ConfigurationProperty(footerProperty, IsRequired = false, DefaultValue = "----------------------------------------")] public string Footer { get { return (string)base[footerProperty]; } set { base[footerProperty] = value; } } /// <summary> /// Gets and sets the formatter name. /// </summary> [ConfigurationProperty(formatterNameProperty, IsRequired = false)] [Reference(typeof(NameTypeConfigurationElementCollection<FormatterData, CustomFormatterData>), typeof(FormatterData))] public string Formatter { get { return (string)base[formatterNameProperty]; } set { base[formatterNameProperty] = value; } } /// <summary> /// Gets and sets the header. /// </summary> [ConfigurationProperty(headerProperty, IsRequired = false, DefaultValue = "----------------------------------------")] public string Header { get { return (string)base[headerProperty]; } set { base[headerProperty] = value; } } /// <summary> /// Exists Behavior /// </summary> [ConfigurationProperty(RollFileExistsBehaviorPropertyName)] public RollFileExistsBehavior RollFileExistsBehavior { get { return (RollFileExistsBehavior)this[RollFileExistsBehaviorPropertyName]; } set { this[RollFileExistsBehaviorPropertyName] = value; } } /// <summary> /// Roll Intervall /// </summary> [ConfigurationProperty(RollIntervalPropertyName)] public RollInterval RollInterval { get { return (RollInterval)this[RollIntervalPropertyName]; } set { this[RollIntervalPropertyName] = value; } } /// <summary> /// Roll Size KB /// </summary> [ConfigurationProperty(RollSizeKBPropertyName)] public int RollSizeKB { get { return (int)this[RollSizeKBPropertyName]; } set { this[RollSizeKBPropertyName] = value; } } /// <summary> /// Time stamp /// </summary> [ConfigurationProperty(TimeStampPatternPropertyName, DefaultValue = "yyyy-MM-dd")] public string TimeStampPattern { get { return (string)this[TimeStampPatternPropertyName]; } set { this[TimeStampPatternPropertyName] = value; } } /// <summary> /// Max rolled files /// </summary> [ConfigurationProperty(MaxArchivedFilesPropertyName)] public int MaxArchivedFiles { get { return (int)this[MaxArchivedFilesPropertyName]; } set { this[MaxArchivedFilesPropertyName] = value; } } protected override TraceListener CoreBuildTraceListener(LoggingSettings settings) { var formatter = this.BuildFormatterSafe(settings, this.Formatter); return new AutoCategoryRollingFlatFileTraceListener( this.FileName, this.Header, this.Footer, formatter, this.RollSizeKB, this.TimeStampPattern, this.RollFileExistsBehavior, this.RollInterval, this.MaxArchivedFiles); } } #endregion
注册代码如下
1、代码注册
Logger.Writer.Configure(config => { AutoCategoryRollingFlatFileTraceListener acListener = new AutoCategoryRollingFlatFileTraceListener("../applog/auto/auto.log", rollFileExistsBehavior: RollFileExistsBehavior.Increment, rollInterval: RollInterval.Minute, timeStampPattern: "yyyyMMddHHmm"); config.SpecialSources.Unprocessed.AddAsynchronousTraceListener(acListener);//异步执行 });
2、配置方式
<loggingConfiguration name="" tracingEnabled="true" defaultCategory="General"> <listeners> <add name="AutoCategoryRollingFlatFileTraceListener" type="WebAPIDemo.AutoCategoryRollingFlatFileTraceListener, WebAPIDemo" listenerDataType="WebAPIDemo.AutoCategoryRollingFlatFileTraceListenerData, WebAPIDemo" fileName="../applog/auto/auto.log" formatter="Text Formatter" rollFileExistsBehavior="Increment" rollInterval="Minute" rollSizeKB="1" timeStampPattern="yyyyMMddHHmm" asynchronous="true" asynchronousDisposeTimeout="infinite" /> </listeners> <formatters> <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging" template="Timestamp: {timestamp(local)}{newline} Message: {message}{newline} Category: {category}{newline} Priority: {priority}{newline} EventId: {eventid}{newline} Severity: {severity}{newline} Title:{title}{newline} Extended Properties: {dictionary({key} - {value}{newline})}" name="Text Formatter" /> </formatters> <categorySources> <add switchValue="All" name="General"> <listeners> </listeners> </add> </categorySources> <specialSources> <allEvents switchValue="All" name="All Events" /> <notProcessed switchValue="All" name="Unprocessed Category"> <listeners> <add name="AutoCategoryRollingFlatFileTraceListener" /> </listeners> </notProcessed> <errors switchValue="All" name="Logging Errors & Warnings" /> </specialSources> </loggingConfiguration>
Logger.Write(string.Format("Get Single Product By ID:{0}", id), "Product");//Product在config中有配置 Logger.Write(string.Format("Get Single Product By ID:{0}", id), "ProductU");//ProductU在config中未配置 Logger.Write(string.Format("Get Single Product By ID:{0}", id), new List<string>() { "ProductX", "ProductXX" });//ProductX、ProductXX在config中未配置