zoukankan      html  css  js  c++  java
  • 做个极简的文本日志收集

      或许大家会疑问,已经有了强大的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()}
    ");
            }    

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

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

      我想了一下,那就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();
            }

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

     
  • 相关阅读:
    推荐:Markdown编辑软件 --- 小书匠
    GoldenDict词典配置
    ivew admin
    FineUI学习网址
    博客主题说明网址
    Xshell,Xftp免费版下载方法
    linux 常用命令及操作
    Oracle数据库随机取某条记录的一个字段值
    service和ingress
    centos7离线安装docker (二进制)
  • 原文地址:https://www.cnblogs.com/hambert/p/13219227.html
Copyright © 2011-2022 走看看