Encapsulate a logging tool class LogUtil based on NLog+NLog.Mongo

Encapsulates a logging tool class LogUtil based on NLog+NLog.Mongo. The code is relatively simple. It mainly integrates the configuration of mongotorget and FileTarget into the class. At the same time, it uses cache dependency to determine whether to re create the Logger class. The complete code is as follows:

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>
///Log tool class (based on NLog.Mongo component)
///Author: Zuo Wenjun
/// 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("Log configuration cache dependency file does not exist:" + 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>
    ///Only one DB will be used for the same project, so it is not open to the public. The default DB is used
    /// </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);//Decrypt string, no decryption required if not encrypted
                    }

                    LoggingConfiguration config = new LoggingConfiguration();

                    #region configures the log output object of 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);//Wrap as asynchronous output object for asynchronous log writing
                        }

                        LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, mongoTargetNew);
                        config.LoggingRules.Add(rule1);
                    }
                    catch
                    { }

                    #endregion

                    #Log output object of region configuration 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);//Wrap as asynchronous output object for asynchronous log writing
                            }

                            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; }
        }
    }


}

The purpose of encapsulating this log tool class is to ensure the uniformity of log format, and it can be quickly copied to each project for use, without the need for configuration files or inconsistency of log record information caused by configuration file modification.

It can be seen from the code that if the attribute changes, the cache ID will be invalid, which means that the Logger object will be regenerated, thus ensuring that the Logger time is the same as the set rule.

Another point is AsyncWriteLog, which is an asynchronous logging function. If it is based on a configuration file, you only need to change the configuration async="true" in the configuration file targets to be asynchronous. The default or write false is synchronization, but how to realize asynchronous network in code is not introduced. I find the key point by analyzing NLOG source code, that is, to wrap the asynchronous target wrapper once through AsyncTargetWrapper.

Tags: C# encoding MongoDB Attribute network

Posted on Wed, 01 Apr 2020 14:15:01 -0700 by yuws