[開源]基於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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...