1. 程式人生 > >基於log4net的日誌元件擴充套件分裝,實現自動記錄互動日誌 XYH.Log4Net.Extend

基於log4net的日誌元件擴充套件分裝,實現自動記錄互動日誌 XYH.Log4Net.Extend

背景:

  隨著公司的專案不斷的完善,功能越來越複雜,服務也越來越多(微服務),公司迫切需要對整個系統的每一個程式的執行情況進行監控,並且能夠實現對自動記錄不同服務間的程式呼叫的互動日誌,以及通一個服務或者專案中某一次執行情況的跟蹤監控

       根據log4net的現有功能滿足不了實際需求,所以需要以log4net為基礎進行分裝完善,現在分裝出了一個基礎的版本,如有不妥之處,多多指點
功能簡介:
  該元件是在log4net的基礎上,進行了一定的擴充套件封裝實現的自動記錄互動日誌功能
  該元件的封裝的目的是解決一下幾個工作中的實際問題
  1、對記錄的日誌內容格式完善
  2、微服務專案中,程式自動記錄不同服務間的呼叫關係,以及出參、入參、執行時間等
  3、同一專案中,不同方法及其層之間的呼叫關係等資訊
  4、其最終目的就是,實現對系統的一個整體監控

主要封裝擴充套件功能點:
1、通過對log4net進行擴充套件,能夠自定義了一些日誌格式顏色內容等
2、通過代理+特性的方式,實現程式自動記錄不同服務間,以及同一程式間的相互呼叫的互動日誌
3、採用佇列的方式實現非同步落地日誌到磁碟檔案

 

主要核心程式碼示例,具體的詳細程式碼,我已經上傳至githut開源專案中,如有需要可以下載瞭解

github原始碼地址:https://github.com/xuyuanhong0902/XYH.Log4Net.Extend.git

代理實現自動記錄方法呼叫的詳細日誌

   /// <summary>
    /// XYH代理實現類.
    /// </summary>
    public class XYHAopProxy : RealProxy
    {
        /// <summary>
        /// 建構函式.
        /// </summary>
        /// <param name="target">目標型別.</param>
        public XYHAopProxy(Type target)
            : base(target)
        {
        }

        /// <summary>
        /// 重寫代理實現.
        /// </summary>
        /// <param name="msg">代理函式</param>
        /// <returns>返回結果</returns>
        public override IMessage Invoke(IMessage methodInvoke)
        {
            //// 方法開始執行時間
            DateTime executeStartTime = System.DateTime.Now;

            //// 方法執行結束時間
            DateTime executeEndTime = System.DateTime.Now;

            IMessage message = null;
            IMethodCallMessage call = methodInvoke as IMethodCallMessage;
            object[] customAttributeArray = call.MethodBase.GetCustomAttributes(false);
            call.MethodBase.GetCustomAttributes(false);

            try
            {
                // 前處理.
                List<IAopAction> proActionList = this.InitAopAction(customAttributeArray, AdviceType.Before);

                //// 方法執行開始記錄日誌
                if (proActionList != null && proActionList.Count > 0  )
                {
                    foreach (IAopAction item in proActionList)
                    {
                        IMessage preMessage = item.PreProcess(methodInvoke, base.GetUnwrappedServer());
                        if (preMessage != null)
                        {
                            message = preMessage;
                        }
                    }

                    if (message != null)
                    {
                        return message;
                    }
                }

                message = Proessed(methodInvoke);

                // 後處理.
                proActionList = this.InitAopAction(customAttributeArray, AdviceType.Around);

                //// 方法執行結束時間
                executeEndTime = System.DateTime.Now;

                //// 方法執行結束記錄日誌
                if (proActionList != null && proActionList.Count > 0)
                {
                    foreach (IAopAction item in proActionList)
                    {
                        item.PostProcess(methodInvoke, message, base.GetUnwrappedServer(), executeStartTime, executeEndTime);
                    }
                }
            }
            catch (Exception ex)
            {
                //// 方法執行結束時間
                executeEndTime = System.DateTime.Now;

                // 異常處理.吃掉異常,不影響主業務
                List<IAopAction> proActionList = this.InitAopAction(customAttributeArray, AdviceType.Around);
                if (proActionList != null && proActionList.Count > 0)
                {
                    foreach (IAopAction item in proActionList)
                    {
                        item.ExceptionProcess(ex, methodInvoke, base.GetUnwrappedServer(), executeStartTime, executeEndTime);
                    }
                }
            }

            return message;
        }

        /// <summary>
        /// 處理方法執行.
        /// </summary>
        /// <param name="methodInvoke">代理目標方法</param>
        /// <returns>代理結果</returns>
        public virtual IMessage Proessed(IMessage methodInvoke)
        {
            IMessage message;
            if (methodInvoke is IConstructionCallMessage)
            {
                message = this.ProcessConstruct(methodInvoke);
            }
            else
            {
                message = this.ProcessInvoke(methodInvoke);
            }
            return message;
        }

        /// <summary>
        /// 普通代理方法執行.
        /// </summary>
        /// <param name="methodInvoke">代理目標方法</param>
        /// <returns>代理結果</returns>
        public virtual IMessage ProcessInvoke(IMessage methodInvoke)
        {
            IMethodCallMessage callMsg = methodInvoke as IMethodCallMessage;
            object[] args = callMsg.Args;   //方法引數                 
            object o = callMsg.MethodBase.Invoke(base.GetUnwrappedServer(), args);  //呼叫 原型類的 方法       

            return new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg);   // 返回型別 Message
        }

        /// <summary>
        /// 建構函式代理方法執行.
        /// </summary>
        /// <param name="methodInvoke">代理目標方法</param>
        /// <returns>代理結果</returns>
        public virtual IMessage ProcessConstruct(IMessage methodInvoke)
        {
            IConstructionCallMessage constructCallMsg = methodInvoke as IConstructionCallMessage;
            IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)methodInvoke);
            RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);

            return constructionReturnMessage;
        }

        /// <summary>
        /// 代理包裝業務處理.
        /// </summary>
        /// <param name="customAttributeArray">代理屬性</param>
        /// <param name="adviceType">處理型別</param>
        /// <returns>結果.</returns>
        public virtual List<IAopAction> InitAopAction(object[] customAttributeArray, AdviceType adviceType)
        {
            List<IAopAction> actionList = new List<IAopAction>();
            if (customAttributeArray != null && customAttributeArray.Length > 0)
            {
                foreach (Attribute item in customAttributeArray)
                {
                    XYHMethodAttribute methodAdviceAttribute = item as XYHMethodAttribute;
                    if (methodAdviceAttribute != null && (methodAdviceAttribute.AdviceType == adviceType))
                    {
                        if (methodAdviceAttribute.ProcessType == ProcessType.None)
                        {
                            continue;
                        }

                        if (methodAdviceAttribute.ProcessType == ProcessType.Log)
                        {
                            actionList.Add(new LogAopActionImpl());
                            continue;
                        }
                    }
                }
            }

            return actionList;
        }
    }

  類註解

  /// <summary>
    /// XYH代理屬性[作用於類].
    /// ************************************
    /// [DecorateSymbol] Class ClassName
    /// ************************************
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class XYHAopAttribute : ProxyAttribute
    {
        public XYHAopAttribute()
        {
        }

        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            XYHAopProxy realProxy = new XYHAopProxy(serverType);
            return realProxy.GetTransparentProxy() as MarshalByRefObject;
        }
    }

  佇列實現非同步日誌落地到磁碟檔案

namespace XYH.Log4Net.Extend
{
    /// <summary>
    /// 通過佇列的方式實現非同步記錄日誌
    /// </summary>
    public sealed class ExtendLogQueue
    {
        /// <summary>
        /// 記錄訊息 佇列
        /// </summary>
        private readonly ConcurrentQueue<LogMessage> extendLogQue;

        /// <summary>
        /// 訊號
        /// </summary>
        private readonly ManualResetEvent extendLogMre;

        /// <summary>
        /// 日誌
        /// </summary>
        private static ExtendLogQueue _flashLog = new ExtendLogQueue();

        /// <summary>
        /// 建構函式
        /// </summary>
        private ExtendLogQueue()
        {
            extendLogQue = new ConcurrentQueue<LogMessage>();
            extendLogMre = new ManualResetEvent(false);
        }

        /// <summary>
        /// 單例例項
        /// </summary>
        /// <returns></returns>
        public static ExtendLogQueue Instance()
        {
            return _flashLog;
        }

        /// <summary>
        /// 另一個執行緒記錄日誌,只在程式初始化時呼叫一次
        /// </summary>
        public void Register()
        {
            Thread t = new Thread(new ThreadStart(WriteLogDispatch));
            t.IsBackground = false;
            t.Start();
        }

        /// <summary>
        /// 從佇列中寫日誌至磁碟
        /// </summary>
        private void WriteLogDispatch()
        {
            while (true)
            {

                //// 如果佇列中還有待寫日誌,那麼直接呼叫寫日誌
                if (extendLogQue.Count > 0)
                {
                    //// 根據佇列寫日誌
                    WriteLog();

                    // 重新設定訊號
                    extendLogMre.Reset();
                }

                //// 如果沒有,那麼等待訊號通知
                extendLogMre.WaitOne();
            }
        }

        /// <summary>
        /// 具體呼叫log4日誌元件實現
        /// </summary>
        private void WriteLog()
        {
            LogMessage msg;
            // 判斷是否有內容需要如磁碟 從列隊中獲取內容,並刪除列隊中的內容
            while (extendLogQue.Count > 0 && extendLogQue.TryDequeue(out msg))
            {
                new LogHandlerImpl(LogHandlerManager.GetILogger(msg.LogSerialNumber)).WriteLog(msg);
            }
        }

        /// <summary>
        /// 日誌入佇列
        /// </summary>
        /// <param name="message">日誌文字</param>
        /// <param name="level">等級</param>
        /// <param name="ex">Exception</param>
        public  void EnqueueMessage(LogMessage logMessage)
        {
            //// 日誌入佇列
            extendLogQue.Enqueue(logMessage);

            // 通知執行緒往磁碟中寫日誌
            extendLogMre.Set();
        }
    }
}

  自定義擴充套件log4net日誌格式內容

namespace XYH.Log4Net.Extend
{
    /// <summary>
    /// 自定義佈局(對log2net日誌元件的佈局自定義擴充套件).
    /// </summary>
    public class HandlerPatternLayout : PatternLayout
    {
        /// <summary>
        /// 建構函式.
        /// </summary>
        public HandlerPatternLayout()
        {
            ///// 機器名稱
            this.AddConverter("LogMachineCode", typeof(LogMachineCodePatternConvert));

            //// 方法名稱
            this.AddConverter("MethodName", typeof(LogMethodNamePatternConvert));

            //// 方法入參
            this.AddConverter("MethodParam", typeof(LogMethodParamConvert));

            //// 方法出參
            this.AddConverter("MethodResult", typeof(LogMethodResultConvert));

            //// 程式名稱
            this.AddConverter("LogProjectName", typeof(LogProjectNamePatternConvert));

            //// IP 地 址
            this.AddConverter("LogIpAddress", typeof(LogServiceIpPatternConvert));

            //// 日誌編號
            this.AddConverter("LogUniqueCode", typeof(LogUniquePatternConvert));

            //// 日誌序列號
            this.AddConverter("LogSerialNumber", typeof(LogSerialNumberPatternConvert));

            //// 呼叫路徑
            this.AddConverter("InvokeName", typeof(LogInvokeNamePatternConvert));

            //// 執行開始時間
            this.AddConverter("ExecuteStartTime", typeof(ExecuteStartTimePatternConvert));

            //// 執行結束時間
            this.AddConverter("ExecuteEndTime", typeof(ExecuteEndTimePatternConvert));

            //// 執行時間
            this.AddConverter("ExecuteTime", typeof(ExecuteTimePatternConvert));
        }
    }
}

  

 

使用說明:
第一步:需要dll檔案引用
需要引用兩個dell檔案:
jeson序列化:Newtonsoft.Json.dll
log4net元件:log4net.dll
log3net擴充套件元件:XYH.Log4Net.Extend.dll

第二步:log4配置檔案配置
主要配置日誌的儲存地址,日誌檔案儲存格式、內容等
下面,給一個參考配置檔案,具體的配置可以根據實際需要自由配置,其配置方式很log4net本身的配置檔案一樣,在此不多說

<log4net>
  <root>
    <!-- 定義記錄的日誌級別[None、Fatal、ERROR、WARN、DEBUG、INFO、ALL]-->
    <level value="ALL"/>
    <!-- 記錄到什麼介質中-->
    <appender-ref ref="LogInfoFileAppender"/>
    <appender-ref ref="LogErrorFileAppender"/>
  </root>
  <!-- name屬性指定其名稱,type則是log4net.Appender名稱空間的一個類的名稱,意思是,指定使用哪種介質-->
  <appender name="LogInfoFileAppender" type="log4net.Appender.RollingFileAppender">
    <!-- 輸出到什麼目錄-->
    <param name="File" value="Log\\LogInfo\\"/>
    <!-- 是否覆寫到檔案中-->
    <param name="AppendToFile" value="true"/>
    <!-- 單個日誌檔案最大的大小-->
    <param name="MaxFileSize" value="10240"/>
    <!-- 備份檔案的個數-->
    <param name="MaxSizeRollBackups" value="100"/>
    <!-- 是否使用靜態檔名-->
    <param name="StaticLogFileName" value="false"/>
    <!-- 日誌檔名-->
    <param name="DatePattern" value="yyyyMMdd".html""/>
    <param name="RollingStyle" value="Date"/>
    <!--佈局-->
    <layout type="XYH.Log4Net.Extend.HandlerPatternLayout">
      <param name="ConversionPattern" value="<HR COLOR=blue>%n%n
                                             日誌編號:%property{LogUniqueCode}  <BR >%n
                                             日誌序列:%property{LogSerialNumber} <BR>%n
                                             機器名稱:%property{LogMachineCode} <BR>%n
                                             IP 地 址:%property{LogIpAddress} <BR>%n
                                             開始時間:%property{ExecuteStartTime} <BR>%n
                                             結束時間:%property{ExecuteEndTime} <BR>%n
                                             執行時間:%property{ExecuteTime} <BR>%n
                                             程式名稱:%property{LogProjectName} <BR>%n
                                             方法名稱:%property{MethodName} <BR>%n
                                             方法入參:%property{MethodParam} <BR>%n
                                             方法出參:%property{MethodResult} <BR>%n
                                             日誌資訊:%m <BR >%n
                                             日誌時間:%d <BR >%n
                                             日誌級別:%-5p <BR >%n
                                             異常堆疊:%exception <BR >%n
                                             <HR Size=1 >"/>
    </layout>
  </appender>
  <!-- name屬性指定其名稱,type則是log4net.Appender名稱空間的一個類的名稱,意思是,指定使用哪種介質-->
  <appender name="LogErrorFileAppender" type="log4net.Appender.RollingFileAppender">
    <!-- 輸出到什麼目錄-->
    <param name="File" value="Log\\LogError\\"/>
    <!-- 是否覆寫到檔案中-->
    <param name="AppendToFile" value="true"/>
    <!-- 備份檔案的個數-->
    <param name="MaxSizeRollBackups" value="100"/>
    <!-- 單個日誌檔案最大的大小-->
    <param name="MaxFileSize" value="10240"/>
    <!-- 是否使用靜態檔名-->
    <param name="StaticLogFileName" value="false"/>
    <!-- 日誌檔名-->
    <param name="DatePattern" value="yyyyMMdd".html""/>
    <param name="RollingStyle" value="Date"/>
    <!--佈局-->
    <layout type="XYH.Log4Net.Extend.HandlerPatternLayout">
      <param name="ConversionPattern" value="<HR COLOR=red>%n
                                             日誌編號:%property{LogUniqueCode}  <BR >%n
                                             日誌序列:%property{LogSerialNumber} <BR>%n
                                             機器名稱:%property{LogMachineCode} <BR>%n
                                             IP 地 址: %property{LogIpAddress} <BR>%n
                                             程式名稱:%property{LogProjectName} <BR>%n
                                             方法名稱:%property{MethodName}<BR>%n
                                             方法入參:%property{MethodParam} <BR>%n
                                             方法出參:%property{MethodResult} <BR>%n
                                             日誌資訊:%m <BR >%n
                                             日誌時間:%d <BR >%n
                                             日誌級別:%-5p <BR >%n
                                             異常堆疊:%exception <BR >%n
                                             <HR Size=1 >"/>
    </layout>
    <filter type="log4net.Filter.LevelRangeFilter">
      <levelMin value="ERROR"/>
      <levelMax value="FATAL"/>
    </filter>
  </appender>
</log4net>

  


第三步:在Global.asax檔案中註冊訊息佇列
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

////註冊日誌佇列
ExtendLogQueue.Instance().Register();
}

第四步:在Global.asax檔案中生成處理日誌序列號
/// <summary>
/// 每一個請求執行開始
/// </summary>
protected void Session_Start() {
//// 記錄獲取建立每一個請求的序列號
/// 如果呼叫放傳遞了序列號,那麼就直接去呼叫放傳遞的序列號
/// 如果呼叫放未傳遞,那麼則生成一個序列號
/// 這樣,在一次請求的頭部傳遞一個該請求的唯一序列號,並在以後的每一個請求都一直傳遞下去
/// 這樣,就能夠通過這個序列號把每一次請求之間的服務或者方法呼叫關係串聯起來
String[] serialNumber = Request.Headers.GetValues("serialNumber");
if (serialNumber!=null && serialNumber.Length>0 && !string.IsNullOrEmpty(serialNumber[0]))
{
Session["LogSerialNumber"] = serialNumber[0];
}
else
{
Session["LogSerialNumber"] = Guid.NewGuid().ToString().Replace("-", "").ToUpper();
}
}

第五步:在需要自動記錄日誌的方法類上加上對應的註解

//// 在需要自動記錄日誌的類上加上 XYHAop註解
[XYHAop]
public class Class2: calssAdd
{
//// 需要記錄自動記錄互動日誌的方法註解 ProcessType.Log

//// 同時該類還必須繼承ContextBoundObject

[XYHMethod(ProcessType.Log)]
public int AddNum(int num1, int num2)
{
}
//// 需要記錄自動記錄互動日誌的方法註解 ProcessType.None,其實不加註解也不會記錄日誌
[XYHMethod(ProcessType.None)]
public int SubNum(int num1, int num2)
{
}
}

第六步:完成上面五步已經能夠實現自動記錄互動日誌了,

 但是在實際使用中我們也會手動記錄一些日誌,本外掛也支援手動記錄日誌的同樣擴充套件效果

目前支援以下6中手動記錄日誌的過載方法基於log4net的日誌元件擴充套件分裝,實現自動記錄互動日誌 XYH.Log4Net.Extend

 /// <summary>
    /// 記錄日誌擴充套件入口
    /// </summary>
    public class XYHLogOperator
    {
        /// <summary>
        /// 新增日誌.
        /// </summary>
        /// <param name="message">日誌資訊物件</param>
        public static void WriteLog(object message)
        {
            new MessageIntoQueue().WriteLog(message);
        }

        /// <summary>
        /// 新增日誌.
        /// </summary>
        /// <param name="message">日誌資訊物件</param>
        /// <param name="level">日誌資訊級別</param>
        public static void WriteLog(object message, LogLevel level)
        {
            new MessageIntoQueue().WriteLog(message, level);
        }

        /// <summary>
        /// 新增日誌.
        /// </summary>
        /// <param name="message">日誌資訊物件</param>
        /// <param name="level">日誌資訊級別</param>
        /// <param name="exception">異常資訊物件</param>
        public static void WriteLog(object message, Exception exception)
        {
            new MessageIntoQueue().WriteLog(message, exception);
        }

        /// <summary>
        /// 新增日誌.
        /// </summary>
        /// <param name="message">日誌資訊物件</param>
        /// <param name="methodName">方法名</param>
        /// <param name="methodParam">方法入參</param>
        /// <param name="methodResult">方法請求結果</param>
        public static void WriteLog(object message, string methodName, object methodParam, object methodResult)
        {
            new MessageIntoQueue().WriteLog(message, methodName, methodParam, methodResult);
        }

        /// <summary>
        /// 新增日誌.
        /// </summary>
        /// <param name="message">日誌資訊物件</param>
        /// <param name="methodName">方法名</param>
        /// <param name="methodParam">方法入參</param>
        /// <param name="methodResult">方法請求結果</param>
        /// <param name="level">日誌記錄級別</param>
        public static void WriteLog(object message, string methodName, object methodParam, object methodResult, LogLevel level)
        {
            new MessageIntoQueue().WriteLog(message, methodName, methodParam, methodResult, level);
        }

        /// <summary>
        /// 新增日誌
        /// </summary>
        /// <param name="extendLogInfor">具體的日誌訊息model</param>
        public static void WriteLog(LogMessage extendLogInfor)
        {
            new MessageIntoQueue().WriteLog(extendLogInfor);
        }
    }
}

  

手動記錄日誌示例:

object message = "一個引數日誌記錄單元測試"; // TODO: 初始化為適當的值
XYHLogOperator.WriteLog(message);

如有問題,歡迎QQ隨時交流
QQ:1315597862

github原始碼地址:https://github.com/xuyuanhong0902/XYH.Log4Net.Extend.git<