[开源]基于Log4Net简单实现KafkaAppender
背景
- 基于之前基于Log4Net本地日志服务简单实现 实现本地日志服务,但是随着项目开发演进,本地日志服务满足不了需求,譬如在预发布环境或者生产环境,不可能让开发人员登录查看本地日志文件分析。
- Kafka+ELK日志服务套件,可以在线日志服务可以解决上述问题,并且提供丰富报表分析等等;
- 具体源码:MasterChief
- Nuget:Install-Package MasterChief.DotNet.Core.KafkaLog
- 欢迎Star,欢迎Issues;
源码
基于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
}基于之前定义接口,来实现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
}
修改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>
使用
- 由于基于上篇说的日志接口,所以可以通过Ioc切换,而且不影响在业务代码调用;
- 基于业务需求,您可以同时落地本地日志,保证网络抖动或者不正常的时候能够正常记录日志;
结语
- 小弟不才,大佬轻拍;
[开源]基于Log4Net简单实现KafkaAppender的更多相关文章
- 基于Log4Net本地日志服务简单实现
背景 项目开发中,我们或多或少会使用诸如NLog,Log4Net,Kafka+ELK等等日志套件: 基于关注点分离原则,业务开发的时候不应该关注日志具体实现:并且后续能方便切换其他日志套件: 这里先实 ...
- CountBoard 是一个基于Tkinter简单的,开源的桌面日程倒计时应用
CountBoard 是一个基于Tkinter简单的,开源的桌面日程倒计时应用. 项目地址 https://github.com/Gaoyongxian666/CountBoard 基本功能 置顶功能 ...
- 基于最简单的FFmpeg包封过程:视频和音频分配器启动(demuxer-simple)
===================================================== 基于最简单的FFmpeg封装工艺的系列文章上市: 最简单的基于FFmpeg的封装格式处理:视 ...
- H2O是开源基于大数据的机器学习库包
H2O是开源基于大数据的机器学习库包 H2O能够让Hadoop做数学,H2O是基于大数据的 统计分析 机器学习和数学库包,让用户基于核心的数学积木搭建应用块代码,采取类似R语言 Excel或JSON等 ...
- 基于最简单的FFmpeg采样读取内存读写:存储转
===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...
- 基于最简单的FFmpeg的AVDevice抽样(屏幕录制)
=====================================================基于最简单的FFmpeg的AVDevice样品文章: 最简单的基于FFmpeg的AVDevic ...
- 基于最简单的FFmpeg采样读取内存读写:内存玩家
===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...
- [开源]基于ffmpeg和libvlc的视频剪辑、播放器
[开源]基于ffmpeg和libvlc的视频剪辑.播放器 以前研究的时候,写过一个简单的基于VLC的视频播放器.后来因为各种项目,有时为了方便测试,等各种原因,陆续加了一些功能,现在集成了视频播放.视 ...
- 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍
GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...
随机推荐
- Java同步简介
Java同步 Java中同步一直都是很重要的问题,对于初学者来说也是不太容易能理解的问题.特在此记录一下有关Java中同步和锁的知识.主要涉及到同步的概念以及Java中解决的办法和简单的例子.有关锁L ...
- Executor框架简介
Executor框架是在Java5中引入的,可以通过该框架来控制线程的启动,执行,关闭,简化并发编程.Executor框架把任务提交和执行解耦,要执行任务的人只需要把任务描述清楚提交即可,任务的执行提 ...
- 【BZOJ 2850】巧克力王国
复习了下KDtree,贴一下新板子233. #include "bits/stdc++.h" using namespace std; inline int read(){ ,k= ...
- LOJ_2305_「NOI2017」游戏 _2-sat
LOJ_2305_「NOI2017」游戏 _2-sat 题意: 给你一个长度为n的字符串S,其中第i个字符为a表示第i个地图只能用B,C两种赛车,为b表示第i个地图只能用A,C两种赛车,为c表示第i个 ...
- Rmq Problem/mex BZOJ3339 BZOJ3585
分析: 一开始没看懂题... 后来想用二分答案却不会验证... 之后,想到用主席树来维护... 建一个权值线段树,维护出这个权值以前所有的点最晚在哪里出现... 之后,查一下是不是比查询区间的l断点大 ...
- 【爆料】-《阿伯丁大学毕业证书》AU一模一样原件
☞阿伯丁大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归&a ...
- Python+Appium 查找 toast 方法的封装
使用场景:在操作应用时常见toast弹框,通过toast弹框信息的获取判断当前的某个操作是否成功 引用的包:from selenium.webdriver.support import expecte ...
- JAVAEE——SpringMVC第一天:介绍、入门程序、架构讲解、SpringMVC整合MyBatis、参数绑定、SpringMVC和Struts2的区别
1. 学习计划 第一天 1.SpringMVC介绍 2.入门程序 3.SpringMVC架构讲解 a) 框架结构 b) 组件说明 4.SpringMVC整合MyBatis 5.参数绑定 a) Sp ...
- 基于 HTML5 的 WebGL 3D 版俄罗斯方块
前言 摘要:2D 的俄罗斯方块已经被人玩烂了,突发奇想就做了个 3D 的游戏机,用来玩俄罗斯方块...实现的基本想法是先在 2D 上实现俄罗斯方块小游戏,然后使用 3D 建模功能创建一个 3D 街机模 ...
- [dotnet] 封装一个同时支持密码/安全密钥认证的SFTP下载器,简单易用。
前言 最近在开发订单对账系统,先从各种支付平台获取订单销售数据,然后与公司商城订单数据进行对账兜底.总体上,各个支付平台提供数据的方式分为两类,一般以接口的方式提供实时数据,比如:webservice ...