基於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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM