背景

  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. 小弟不才,大佬轻拍;

[开源]基于Log4Net简单实现KafkaAppender的更多相关文章

  1. 基于Log4Net本地日志服务简单实现

    背景 项目开发中,我们或多或少会使用诸如NLog,Log4Net,Kafka+ELK等等日志套件: 基于关注点分离原则,业务开发的时候不应该关注日志具体实现:并且后续能方便切换其他日志套件: 这里先实 ...

  2. CountBoard 是一个基于Tkinter简单的,开源的桌面日程倒计时应用

    CountBoard 是一个基于Tkinter简单的,开源的桌面日程倒计时应用. 项目地址 https://github.com/Gaoyongxian666/CountBoard 基本功能 置顶功能 ...

  3. 基于最简单的FFmpeg包封过程:视频和音频分配器启动(demuxer-simple)

    ===================================================== 基于最简单的FFmpeg封装工艺的系列文章上市: 最简单的基于FFmpeg的封装格式处理:视 ...

  4. H2O是开源基于大数据的机器学习库包

    H2O是开源基于大数据的机器学习库包 H2O能够让Hadoop做数学,H2O是基于大数据的 统计分析 机器学习和数学库包,让用户基于核心的数学积木搭建应用块代码,采取类似R语言 Excel或JSON等 ...

  5. 基于最简单的FFmpeg采样读取内存读写:存储转

    ===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...

  6. 基于最简单的FFmpeg的AVDevice抽样(屏幕录制)

    =====================================================基于最简单的FFmpeg的AVDevice样品文章: 最简单的基于FFmpeg的AVDevic ...

  7. 基于最简单的FFmpeg采样读取内存读写:内存玩家

    ===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...

  8. [开源]基于ffmpeg和libvlc的视频剪辑、播放器

    [开源]基于ffmpeg和libvlc的视频剪辑.播放器 以前研究的时候,写过一个简单的基于VLC的视频播放器.后来因为各种项目,有时为了方便测试,等各种原因,陆续加了一些功能,现在集成了视频播放.视 ...

  9. 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍

    GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...

随机推荐

  1. 安卓---RedioButton(单选按钮)、CheckBox(复选按钮)

    <RadioGroup android:layout_width="fill_parent" android:layout_height="wrap_content ...

  2. log.go

    ) //打开日志文件 以及文件操作权限     if err != nil {         return err     }     // 解析日志记录的等级信息     level, err : ...

  3. 【bzoj1758】[Wc2010]重建计划

    Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案, ...

  4. 【状压dp】Bzoj2064 分裂

    Description 背景: 和久必分,分久必和... 题目描述: 中国历史上上分分和和次数非常多..通读中国历史的WJMZBMR表示毫无压力. 同时经常搞OI的他把这个变成了一个数学模型. 假设中 ...

  5. POJ_2104_K-th Number_主席树

    POJ_2104_K-th Number_主席树 题意:给定一个长度为n的序列,m次询问区间第k小 分析: 主席树模板 主席树可以理解成为n棵权值线段树的前缀和 但我们不能建n棵线段树,只需要对于每个 ...

  6. Java中的异常简介

    Java中异常的分类 Java中的异常机制是针对正常运行程序的一个必要补充,一般来说没有加入异常机制,程序也能正常运营,但是,由于入参.程序逻辑的严谨度,总会有期望之外的结果生成,因此加入异常机制的补 ...

  7. .net中的线程同步基础(搬运自CLR via C#)

    线程安全 此类型的所有公共静态(Visual Basic 中为 Shared)成员对多线程操作而言都是安全的.但不保证任何实例成员是线程安全的. 在MSDN上经常会看到这样一句话.表示如果程序中有n个 ...

  8. 关于socket.io的使用

    这段时间学习了socket.io,用它写了小项目,在此总结下它的基本使用方式和一些要点. socket.io是基于Node.js和WebSocket协议的实时通信开源框架,它包括客户端的JavaScr ...

  9. Haskell学习-高阶函数

    原文地址:Haskell学习-高阶函数 高阶函数(higher-order function)就是指可以操作函数的函数,即函数可以作为参数,也可以作为返回结果.有了这两个特性,haskell可以实现许 ...

  10. 基于百度AI开放平台的人脸识别及语音合成

    基于百度AI的人脸识别及语音合成课题 课题需求 (1)人脸识别 在Web界面上传人的照片,后台使用Java技术接收图片,然后对图片进行解码,调用云平台接口识别人脸特征,接收平台返回的人员年龄.性别.颜 ...