[開源]基於Log4Net簡單實現KafkaAppender

来源:https://www.cnblogs.com/MeetYan/archive/2019/04/12/10693545.html
-Advertisement-
Play Games

背景 1. 基於之前 "基於Log4Net本地日誌服務簡單實現" 實現本地日誌服務,但是隨著項目開發演進,本地日誌服務滿足不了需求,譬如在預發佈環境或者生產環境,不可能讓開發人員登錄查看本地日誌文件分析。 2. Kafka+ELK日誌服務套件,可以線上日誌服務可以解決上述問題,並且提供豐富報表分析等 ...


背景

  1. 基於之前基於Log4Net本地日誌服務簡單實現 實現本地日誌服務,但是隨著項目開發演進,本地日誌服務滿足不了需求,譬如在預發佈環境或者生產環境,不可能讓開發人員登錄查看本地日誌文件分析。
  2. Kafka+ELK日誌服務套件,可以線上日誌服務可以解決上述問題,並且提供豐富報表分析等等;
  3. 具體源碼:MasterChief
  4. Nuget:Install-Package MasterChief.DotNet.Core.KafkaLog
  5. 歡迎Star,歡迎Issues;

源碼

  1. 基於Log4Net來實現與kafka通訊Appender

     public class KafkaAppender : AppenderSkeleton
     {
         #region Fields
    
         /// <summary>
         ///     Kafka 生產者
         /// </summary>
         private Producer _kafkaProducer;
    
         #endregion Fields
    
         #region Properties
    
         /// <summary>
         ///     Brokers
         /// </summary>
         public string Brokers { get; set; }
    
         /// <summary>
         ///     Topic
         /// </summary>
         public string Topic { get; set; }
    
         #endregion Properties
    
         #region Methods
    
         /// <summary>
         ///     Initialize the appender based on the options set
         /// </summary>
         /// <remarks>
         ///     <para>
         ///         This is part of the <see cref="T:log4net.Core.IOptionHandler" /> delayed object
         ///         activation scheme. The <see cref="M:log4net.Appender.AppenderSkeleton.ActivateOptions" /> method must
         ///         be called on this object after the configuration properties have
         ///         been set. Until <see cref="M:log4net.Appender.AppenderSkeleton.ActivateOptions" /> is called this
         ///         object is in an undefined state and must not be used.
         ///     </para>
         ///     <para>
         ///         If any of the configuration properties are modified then
         ///         <see cref="M:log4net.Appender.AppenderSkeleton.ActivateOptions" /> must be called again.
         ///     </para>
         /// </remarks>
         public override void ActivateOptions()
         {
             base.ActivateOptions();
             InitKafkaProducer();
         }
    
         /// <summary>
         ///     Subclasses of <see cref="T:log4net.Appender.AppenderSkeleton" /> should implement this method
         ///     to perform actual logging.
         /// </summary>
         /// <param name="loggingEvent">The event to append.</param>
         /// <remarks>
         ///     <para>
         ///         A subclass must implement this method to perform
         ///         logging of the <paramref name="loggingEvent" />.
         ///     </para>
         ///     <para>
         ///         This method will be called by <see cref="M:DoAppend(LoggingEvent)" />
         ///         if all the conditions listed for that method are met.
         ///     </para>
         ///     <para>
         ///         To restrict the logging of events in the appender
         ///         override the <see cref="M:PreAppendCheck()" /> method.
         ///     </para>
         /// </remarks>
         protected override void Append(LoggingEvent loggingEvent)
         {
             try
             {
                 var message = GetLogMessage(loggingEvent);
                 var topic = GetTopic(loggingEvent);
    
                 _ = _kafkaProducer.SendMessageAsync(topic, new[] {new Message(message)});
             }
             catch (Exception ex)
             {
                 ErrorHandler.Error("KafkaProducer SendMessageAsync", ex);
             }
         }
    
         /// <summary>
         ///     Raises the Close event.
         /// </summary>
         /// <remarks>
         ///     <para>
         ///         Releases any resources allocated within the appender such as file handles,
         ///         network connections, etc.
         ///     </para>
         ///     <para>
         ///         It is a programming error to append to a closed appender.
         ///     </para>
         /// </remarks>
         protected override void OnClose()
         {
             base.OnClose();
             StopKafkaProducer();
         }
    
         private string GetLogMessage(LoggingEvent loggingEvent)
         {
             var builder = new StringBuilder();
             using (var writer = new StringWriter(builder))
             {
                 Layout.Format(writer, loggingEvent);
    
                 if (Layout.IgnoresException && loggingEvent.ExceptionObject != null)
                     writer.Write(loggingEvent.GetExceptionString());
    
                 return writer.ToString();
             }
         }
    
         private string GetTopic(LoggingEvent loggingEvent)
         {
             return string.IsNullOrEmpty(Topic) ? Path.GetFileNameWithoutExtension(loggingEvent.Domain) : Topic;
         }
    
         /// <summary>
         ///     初始化Kafka 生產者
         /// </summary>
         private void InitKafkaProducer()
         {
             try
             {
                 if (string.IsNullOrEmpty(Brokers)) Brokers = "http://localhost:9200";
    
                 if (_kafkaProducer == null)
                 {
                     var brokers = new Uri(Brokers);
                     var kafkaOptions = new KafkaOptions(brokers)
                     {
                         Log = new KafkaLog()
                     };
                     _kafkaProducer = new Producer(new BrokerRouter(kafkaOptions));
                 }
             }
             catch (Exception ex)
             {
                 ErrorHandler.Error("InitKafkaProducer", ex);
             }
         }
    
         /// <summary>
         ///     停止生產者
         /// </summary>
         private void StopKafkaProducer()
         {
             try
             {
                 _kafkaProducer?.Stop();
             }
             catch (Exception ex)
             {
                 ErrorHandler.Error("StopKafkaProducer", ex);
             }
         }
    
         #endregion Methods
     }
    
  2. 基於之前定義介面,來實現kafkaLogService

    public sealed class KafkaLogService : ILogService
    {
        #region Constructors
    
        /// <summary>
        ///     Initializes the <see cref="FileLogService" /> class.
        /// </summary>
        static KafkaLogService()
        {
            KafkaLogger = LogManager.GetLogger(KafkaLoggerName);
        }
    
        #endregion Constructors
    
        #region Fields
    
        /// <summary>
        ///     Kafka logger name
        /// </summary>
        public const string KafkaLoggerName = "KafkaLogger";
    
        /// <summary>
        ///     Kafka logger
        /// </summary>
        public static readonly ILog KafkaLogger;
    
        #endregion Fields
    
        #region Methods
    
        /// <summary>
        ///     Debug記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        public void Debug(string message)
        {
            if (KafkaLogger.IsDebugEnabled) KafkaLogger.Debug(message);
        }
    
        /// <summary>
        ///     Debug記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="ex">異常信息</param>
        public void Debug(string message, Exception ex)
        {
            if (KafkaLogger.IsDebugEnabled) KafkaLogger.Debug(message, ex);
        }
    
        /// <summary>
        ///     Error記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        public void Error(string message)
        {
            if (KafkaLogger.IsErrorEnabled) KafkaLogger.Error(message);
        }
    
        /// <summary>
        ///     Error記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="ex">異常信息</param>
        public void Error(string message, Exception ex)
        {
            if (KafkaLogger.IsErrorEnabled) KafkaLogger.Error(message, ex);
        }
    
        /// <summary>
        ///     Fatal記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        public void Fatal(string message)
        {
            if (KafkaLogger.IsFatalEnabled) KafkaLogger.Fatal(message);
        }
    
        /// <summary>
        ///     Fatal記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="ex">異常信息</param>
        public void Fatal(string message, Exception ex)
        {
            if (KafkaLogger.IsFatalEnabled) KafkaLogger.Fatal(message, ex);
        }
    
        /// <summary>
        ///     Info記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        public void Info(string message)
        {
            if (KafkaLogger.IsInfoEnabled) KafkaLogger.Info(message);
        }
    
        /// <summary>
        ///     Info記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="ex">異常信息</param>
        public void Info(string message, Exception ex)
        {
            if (KafkaLogger.IsInfoEnabled) KafkaLogger.Info(message, ex);
        }
    
        /// <summary>
        ///     Warn記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        public void Warn(string message)
        {
            if (KafkaLogger.IsWarnEnabled) KafkaLogger.Warn(message);
        }
    
        /// <summary>
        ///     Warn記錄
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="ex">異常信息</param>
        public void Warn(string message, Exception ex)
        {
            if (KafkaLogger.IsWarnEnabled) KafkaLogger.Warn(message, ex);
        }
    
        #endregion Methods
    }
  3. 修改Log4Net.Config,定義Kafka的Topic以及Brokers

        <appender name="KafkaAppender" type="MasterChief.DotNet.Core.KafkaLog.KafkaAppender, MasterChief.DotNet.Core.KafkaLog">
            <param name="Topic" value="beats" />
            <param name="Brokers" value="http://localhost:9092" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
            </layout>
        </appender>

使用

  1. 由於基於上篇說的日誌介面,所以可以通過Ioc切換,而且不影響在業務代碼調用;
  2. 基於業務需求,您可以同時落地本地日誌,保證網路抖動或者不正常的時候能夠正常記錄日誌;

結語

  1. 小弟不才,大佬輕拍;

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 什麼是線程池? 線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啟動這些任務。 為什麼要用線程池? 降低資源消耗 通過重覆利用已創建的線程降低線程創建和銷毀造成的消耗。 提高響應速度 當任務到達時,任務可以不需要等到線程創建就能立即執行。 提高線程的可管理性 線程是 ...
  • 結果: Loop 1Loop 2Loop 3Loop 4Loop 5Loop 6迴圈執行完啦 out of while loop Loop 1Loop 2 out of while loop 結論:while迴圈正常執行完不會執行else裡邊的代碼,如果while迴圈被break中斷則會執行else ...
  • 今天給大家帶來一篇如何評價模型的好壞以及模型的得分 最下麵的代碼最有用 一、錯誤率與精度(accuracy 準確) 錯誤率和精度是分類任務中最常用的兩種性能度量,既適用於二分類任務,也適用於多分類任務。錯誤率是分類錯誤的樣本數占樣本總數的比例,精度則是分類正確的樣本數占樣本總數的比例。 from s ...
  • 上一篇講到方法的調用和簡單的構造方法,今天繼續加深,加參數或者該參數; package sklx; public class Car{ //設三個屬性 private String 品牌; private int 價格; private String 顏色; //修改屬性參數方法 public Ca ...
  • 我們面試中經常會被問到多線程相關知識,這一塊內容往淺了說大家都會,但是一問到底層實現原理,我們往往就一臉懵逼。 這段時間準備好好學習多線程,接下來會寫一系列關於多線程的知識。 我們首先要瞭解線程,百度百科這麼介紹:線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中 ...
  • 思路 **先考慮一條鏈的情況怎麼做。** 因為只有兩個子樹,並且兩個子樹都是鏈。所以可以把這兩條鏈找出來,然後$sort$一下。合併起來。 **然後推廣到樹上** 對於每一棵樹都可以按照和上面同樣的方法合併成一條鏈。 ...
  • 一. 概述 本篇開始進入IS4實戰學習,從第一個示例開始,該示例是 “使用客戶端憑據保護API”,這是使用IdentityServer保護api的最基本場景。該示例涉及到三個項目包括:IdentityServer項目、API項目、Client項目,都有自己的宿主,為了方便開發,放在了一個解決方案下( ...
  • 導入導出的方法以及引用,可以自行創建一個幫助類 using System;using NPOI.SS.UserModel;using NPOI.XSSF.UserModel;using NPOI.HSSF.UserModel;using System.IO;using System.Data;usi ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...