封裝一個基於NLog+NLog.Mongo的日誌記錄工具類LogUtil,代碼比較簡單,主要是把MongoTarget的配置、FileTarget的配置集成到類中,同時利用緩存依賴來判斷是否需要重新創建Logger類,完整代碼如下: 封裝這個日誌工具類的目的就是為了保證日誌格式的統一,同時可以快速的 ...
封裝一個基於NLog+NLog.Mongo的日誌記錄工具類LogUtil,代碼比較簡單,主要是把MongoTarget的配置、FileTarget的配置集成到類中,同時利用緩存依賴來判斷是否需要重新創建Logger類,完整代碼如下:
using NLog; using NLog.Config; using NLog.Mongo; using NLog.Targets; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Web; using System.Collections.Concurrent; using NLog.Targets.Wrappers; /// <summary> /// 日誌工具類(基於NLog.Mongo組件) /// Author:左文俊 /// Date:2017/12/11 /// </summary> public class LogUtil { private NLog.Logger _Logger = null; private const string cacheKey_NLogConfigFlag = "NLogConfigFlag"; private const string defaultMongoDbName = "SysLog"; private static readonly object syncLocker = new object(); private static readonly ConcurrentDictionary<string, LogUtil> cacheLogUitls = new ConcurrentDictionary<string, LogUtil>(); private string loggerCacheDependencyFilePath = ""; private bool needWriteLogToFile = true; private string mongoDbName = defaultMongoDbName; private string mongoDbCollectionName = ""; private bool asyncWriteLog = true; public static LogUtil GetInstance(string mongoDbCollName, string loggerCacheDependencyFilePath = null, bool needWriteLogToFile = true) { string key = string.Format("{0}_{1}", defaultMongoDbName, mongoDbCollName); return cacheLogUitls.GetOrAdd(key, new LogUtil() { LoggerCacheDependencyFilePath = string.IsNullOrEmpty(loggerCacheDependencyFilePath) ? HttpContext.Current.Server.MapPath("~/Web.config") : loggerCacheDependencyFilePath, NeedWriteLogToFile = needWriteLogToFile, MongoDbName = defaultMongoDbName, MongoDbCollectionName = mongoDbCollName }); } public string LoggerCacheDependencyFilePath { get { return loggerCacheDependencyFilePath; } set { if (!File.Exists(value)) { throw new FileNotFoundException("日誌配置緩存依賴文件不存在:" + value); } string oldValue = loggerCacheDependencyFilePath; loggerCacheDependencyFilePath = value; PropertyChanged(oldValue, loggerCacheDependencyFilePath); } } public bool NeedWriteLogToFile { get { return needWriteLogToFile; } set { bool oldValue = needWriteLogToFile; needWriteLogToFile = value; PropertyChanged(oldValue, needWriteLogToFile); } } public string MongoDbCollectionName { get { return mongoDbCollectionName; } set { string oldValue = mongoDbCollectionName; mongoDbCollectionName = value; PropertyChanged(oldValue, mongoDbCollectionName); } } /// <summary> /// 同一個項目只會用一個DB,故不對外公開,取預設DB /// </summary> private string MongoDbName { get { return mongoDbName; } set { string oldValue = mongoDbName; mongoDbName = value; PropertyChanged(oldValue, mongoDbName); } } public bool AsyncWriteLog { get { return asyncWriteLog; } set { bool oldValue = asyncWriteLog; asyncWriteLog = value; PropertyChanged(oldValue, asyncWriteLog); } } private void PropertyChanged<T>(T oldValue, T newValue) where T : IEquatable<T> { if (!oldValue.Equals(newValue) && _Logger != null) { lock (syncLocker) { _Logger = null; } } } private Logger GetLogger() { if (_Logger == null || HttpRuntime.Cache[cacheKey_NLogConfigFlag] == null) { lock (syncLocker) { if (_Logger == null || HttpRuntime.Cache[cacheKey_NLogConfigFlag] == null) { string mongoDbConnectionSet = ConfigUtil.GetAppSettingValue("MongoDbConnectionSet"); if (!string.IsNullOrEmpty(mongoDbConnectionSet)) { mongoDbConnectionSet = AESDecrypt(mongoDbConnectionSet);//解密字元串,若未加密則無需解密 } LoggingConfiguration config = new LoggingConfiguration(); #region 配置MONGODB的日誌輸出對象 try { MongoTarget mongoTarget = new MongoTarget(); mongoTarget.ConnectionString = mongoDbConnectionSet; mongoTarget.DatabaseName = mongoDbName; mongoTarget.CollectionName = mongoDbCollectionName; mongoTarget.IncludeDefaults = false; AppendLogMongoFields(mongoTarget.Fields); Target mongoTargetNew = mongoTarget; if (AsyncWriteLog) { mongoTargetNew = WrapWithAsyncTargetWrapper(mongoTarget);//包裝為非同步輸出對象,以便實現非同步寫日誌 } LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, mongoTargetNew); config.LoggingRules.Add(rule1); } catch { } #endregion #region 配置File的日誌輸出對象 if (NeedWriteLogToFile) { try { FileTarget fileTarget = new FileTarget(); fileTarget.Layout = @"[${date}] <${threadid}> - ${level} - ${event-context:item=Source} - ${event-context:item=UserID}: ${message}; StackTrace:${stacktrace};Other1:${event-context:item=Other1};Other2:${event-context:item=Other2};Other3:${event-context:item=Other3}"; string procName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; fileTarget.FileName = "${basedir}/Logs/" + procName + ".log"; fileTarget.ArchiveFileName = "${basedir}/archives/" + procName + ".{#}.log"; fileTarget.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; fileTarget.ArchiveAboveSize = 1024 * 1024 * 10; fileTarget.ArchiveDateFormat = "yyyyMMdd"; fileTarget.ArchiveEvery = FileArchivePeriod.Day; fileTarget.MaxArchiveFiles = 30; fileTarget.ConcurrentWrites = true; fileTarget.KeepFileOpen = false; fileTarget.Encoding = System.Text.Encoding.UTF8; Target fileTargetNew = fileTarget; if (AsyncWriteLog) { fileTargetNew = WrapWithAsyncTargetWrapper(fileTarget);//包裝為非同步輸出對象,以便實現非同步寫日誌 } LoggingRule rule2 = new LoggingRule("*", LogLevel.Debug, fileTargetNew); config.LoggingRules.Add(rule2); } catch { } } #endregion LogManager.Configuration = config; _Logger = LogManager.GetCurrentClassLogger(); HttpRuntime.Cache.Insert(cacheKey_NLogConfigFlag, "Nlog", new System.Web.Caching.CacheDependency(loggerCacheDependencyFilePath)); } } } return _Logger; } private void AppendLogMongoFields(IList<MongoField> mongoFields) { mongoFields.Clear(); Type logPropertiesType = typeof(SysLogInfo.LogProperties); foreach (var pro in typeof(SysLogInfo).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (pro.PropertyType == logPropertiesType) continue; string layoutStr = string.Empty; //"${event-context:item=" + pro.Name + "}"; if (pro.Name.Equals("ThreadID") || pro.Name.Equals("Level") || pro.Name.Equals("MachineName")) { layoutStr = "${" + pro.Name.ToLower() + "}"; } else if (pro.Name.Equals("LogDT")) { layoutStr = "${date:format=yyyy-MM-dd HH\\:mm\\:ss}"; } else if (pro.Name.Equals("Msg")) { layoutStr = "${message}"; } if (!string.IsNullOrEmpty(layoutStr)) { mongoFields.Add(new MongoField(pro.Name, layoutStr, pro.PropertyType.Name)); } } } private Target WrapWithAsyncTargetWrapper(Target target) { var asyncTargetWrapper = new AsyncTargetWrapper(); asyncTargetWrapper.WrappedTarget = target; asyncTargetWrapper.Name = target.Name; target.Name = target.Name + "_wrapped"; target = asyncTargetWrapper; return target; } private LogEventInfo BuildLogEventInfo(LogLevel level, string msg, string source, string uid, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null) { var eventInfo = new LogEventInfo(); eventInfo.Level = level; eventInfo.Message = msg; eventInfo.Properties["DetailTrace"] = detailTrace; eventInfo.Properties["Source"] = source; eventInfo.Properties["Other1"] = other1; eventInfo.Properties["Other2"] = other2; eventInfo.Properties["Other3"] = other3; eventInfo.Properties["UserID"] = uid; return eventInfo; } public void Info(string msg, string source, string uid, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null) { try { var eventInfo = BuildLogEventInfo(LogLevel.Info, msg, source, uid, detailTrace, other1, other2, other3); var logger = GetLogger(); logger.Log(eventInfo); } catch { } } public void Warn(string msg, string source, string uid, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null) { try { var eventInfo = BuildLogEventInfo(LogLevel.Warn, msg, source, uid, detailTrace, other1, other2, other3); var logger = GetLogger(); logger.Log(eventInfo); } catch { } } public void Error(string msg, string source, string uid, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null) { try { var eventInfo = BuildLogEventInfo(LogLevel.Error, msg, source, uid, detailTrace, other1, other2, other3); var logger = GetLogger(); logger.Log(eventInfo); } catch { } } public void Error(Exception ex, string source, string uid, string other1 = null, string other2 = null, string other3 = null) { try { var eventInfo = BuildLogEventInfo(LogLevel.Error, ex.Message, source, uid, ex.StackTrace, other1, other2, other3); var logger = GetLogger(); logger.Log(eventInfo); } catch { } } public void Log(LogLevel level, string msg, string source, string uid, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null) { try { var eventInfo = BuildLogEventInfo(level, msg, source, uid, detailTrace, other1, other2, other3); var logger = GetLogger(); logger.Log(eventInfo); } catch { } } public class SysLogInfo { public DateTime LogDT { get; set; } public int ThreadID { get; set; } public string Level { get; set; } public string Msg { get; set; } public string MachineName { get; set; } public LogProperties Properties { get; set; } public class LogProperties { public string Source { get; set; } public string DetailTrace { get; set; } public string UserID { get; set; } public string Other1 { get; set; } public string Other2 { get; set; } public string Other3 { get; set; } } } }
封裝這個日誌工具類的目的就是為了保證日誌格式的統一,同時可以快速的複製到各個項目中使用,而省去需要配置文件或因配置文件修改導致日誌記錄信息不一致的情況。
從代碼中可以看出,若一旦屬性發生改變,則緩存標識會失效,意味著會重新生成Logger對象,這樣保證了Logger時刻與設置的規則相同。
另一點就是非同步日誌記錄功能AsyncWriteLog,如果是基於配置文件,則只需要更改配置文件targets中配置async="true"即為非同步。預設或寫false都為同步,而代碼上如何實現非同步網上並沒有介紹,我通過分析NLOG源代碼找到關鍵點,即通過AsyncTargetWrapper非同步目標包裹器來包裝一次即可。