其他分享
首页 > 其他分享> > 做个极简的文本日志收集

做个极简的文本日志收集

作者:互联网

  或许大家会疑问,已经有了强大的log4net,nlog等,为啥还要自己折腾写日志呢,那是因为最近我有个需求,把所有的操作记录到日志文件里,然后运维每天自动把这些日志同步到kibana做日志收集,然后分析处理。

  其实一开始我是想直接让他们做一个接口,然后我每次的操作都调用一次他们的接口,这样也可以同步日志,但老大认为这种高频低价值并且无需实时的数据没必要动用接口,这样其实是一种浪费,先写日志,然后统一处理更高效。我想了一下,貌似确实是没必要动用接口。

  那么自己写日志咋写呢,一开始我是直接简单粗暴每来一条日志,就写一次文件:

        /// <summary>
        /// 记录推送日志
        /// </summary>
        /// <param name="messageId">消息ID</param>
        /// <param name="status">推送状态</param>
        /// <param name="brand">品牌</param>
        public static void AddPushLog(string messageId, PushStatus status, string brand)
        {
            if (string.IsNullOrEmpty(messageId))
            {
                return;
            }

            var now = DateTime.Now;
            var fileName = $"{PassportConfig.Env.ContentRootPath}/pushlog/{now:yyyyMMdd}.csv";
            File.AppendAllTextAsync(fileName, $"{messageId},{brand},{(int)status},{now.ToUnixTimestamp()}\n");
        }    

  但是没过几天,我发现日志有点问题,会出现日志黏连现象,就是两条日志黏在一起了,之所以这样,是因为写入太频繁了,导致两个写入同时发生了,所以他们的写入就可能黏在一起。

  那么该如何避免这种并行事件呢,而且每来一条日志就写一次日志确实性能也不佳。

  我想了一下,那就1分钟写入一次吧,写入先放在生产者列表,然后搞个后台任务,每分钟去查看一下生产者列表,发现有日志,则把生产者交给消费者,然后生产者清空后继续生产,消费者就把这批次的日志批量写入日志文件,代码如下:

        //日志生产者
        private static List<string> _logsProducer = new List<string>();
        //日志消费者
        private static List<string> _logsConsumer;
        //日志临时存放,用来交换生产者和消费者
        private static List<string> _logsTemp = new List<string>();

        /// <summary>
        /// 记录推送日志
        /// </summary>
        /// <param name="messageId">消息ID</param>
        /// <param name="status">推送状态</param>
        /// <param name="brand">品牌</param>
        public static void AddPushLog(string messageId, PushStatus status, string brand)
        {
            if (!string.IsNullOrEmpty(messageId))
            {
                _logsProducer.Add($"{messageId},{brand},{(int)status},{DateTime.Now.ToUnixTimestamp()}");
            }
        }

        /// <summary>
        /// 清空push日志,写入到push日志文件
        /// </summary>
        public static void FlushPushLog()
        {
            //没日志则不消费
            if (_logsProducer.Count == 0)
            {
                return;
            }

            _logsConsumer = _logsProducer;
            _logsProducer = _logsTemp;
            var now = DateTime.Now;
            var fileName = $"{PassportConfig.Env.ContentRootPath}/pushlog/{now:yyyyMMdd}.csv";
            File.AppendAllLines(fileName, _logsConsumer);
            _logsConsumer.Clear();
            _logsTemp = _logsConsumer;
        }

  代码很简洁,发现有日志时,

_logsConsumer = _logsProducer,这时候生产者和消费者指向了同一个列表,这时候
_logsProducer继续增加日志的话,
_logsConsumer也会增加,因为他们指向了同一片内存,然后
_logsProducer = _logsTemp,因为
_logsTemp是空的,所以生产者相当于被清空了,这样就完成了生产者和消费者交换数据的操作,然后消费者得到了生产者的数据后,就把日志通过
File.AppendAllLines一次性写入到日志文件,我试过了,几万行几十行日志写入到文本也是一下子的事情,写完日志后清空消费者日志列表,然后
_logsTemp = _logsConsumer就把消费者列表交还给临时列表了,这样操作就结束了。大家可以发现全程无锁,全程只用到了两个List,另一个List只是中转用的,并没有new出来的。 
 
  那么接下来只需要搞一个后台任务去定时清空写入日志即可,代码如下:

    /// <summary>
    /// 每分钟写一次push日志
    /// </summary>
    public class PushLogService : BackgroundService
    {
        private readonly ILogger<PushLogService> _logger;
        /// <summary>
        /// 每分钟写一次push日志
        /// </summary>
        private const int Sleep = 60000;

        public PushLogService(ILogger<PushLogService> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    PushHelper.FlushPushLog();
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "PushLogError");
                }
                finally
                {
                    await Task.Delay(Sleep);
                }
            }
        }
    }

  这样就完事了吗,其实还有个问题,万一网站关闭没来得及清空日志咋办,不怕,在网站关闭事件里清空一下日志就好了:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHostApplicationLifetime applicationLifetime)
        {
            applicationLifetime.ApplicationStopping.Register(OnShutdown);
        }

        private void OnShutdown()
        {
            PushHelper.FlushPushLog();
        }

  这样就可以在网站关闭前清空日志了。

 

标签:logsProducer,做个,List,写入,logsConsumer,清空,日志,文本
来源: https://www.cnblogs.com/hambert/p/13219227.html