日志埋点的实现(本地生成txt文件,异步读取上传)

最近工作过程中需要实现一个日志埋点的功能,采集用户行为及相关行为Log以便后续的报表分析。

首先整理下实现日志埋点必须具备的功能:

1.行为采集注册-

2.行为采集实时写入

3.行为采集异步上传

实现过程中可能会出现的问题:文本文件追加和读取的并发问题。

具体实现:

private static object loker = new object();

private static System.Timers.Timer aTimer = new System.Timers.Timer();

static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();

 private static string file = System.Environment.CurrentDirectory + "Log";

 

 1.注册获取登陆用户的信息,开启异步线程,定时读取本地生成的txt文件上传到数据库,我采用mongoDB存储埋点信息。


        public bool RegisterCollect()
        {
            var response = Register(注册的参数); //读取采集配置信息(是否采集,是否上传,上传频率)
            if (response.Sucess)
            {
                AddActionRecord();
                //根据读取到的配置信息 开启异步线程定时读取本地文件
                AddActionRecord();
            }

//注册成功后判断文件夹路径是否存在,不存在则创建

        private CheckDirctory()
        {
            if (!Directory.Exists(file))
            {
                Directory.CreateDirectory(file);
            }
        }

        public void AddActionRecord()
        {
            CheckDirctory()
            aTimer.Interval = 60000 * 上传频率;

            aTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);

            aTimer.Enabled = true;

        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            lock (loker)
            {
                UploadTxtRecord();
            }
        }

        private void UploadTxtRecord()
        {
            var query = (from f in Directory.GetFiles(file, "*.txt")

                         let fi = new FileInfo(f)

                         orderby fi.CreationTime descending

                         select fi.FullName).ToList();

            var logFilePath = query.FirstOrDefault();

            if (query.Count == 0 || FileStatusHelper.IsFileOccupied(logFilePath))
            {
                return;
            }

            try
            {
                LogWriteLock.EnterWriteLock();

                FileStream fileStream = new FileStream(logFilePath, FileMode.Open, FileAccess.ReadWrite);

                StreamReader reader = new StreamReader(fileStream, Encoding.GetEncoding("UTF-8"));

                string line = "";

                while ((line = reader.ReadLine()) != null)

                {
                    //调用服务接口,写入埋点信息到数据库中

                }
                reader.Close();

                fileStream.Close();

                File.Delete(logFilePath);
            }
            catch (Exception ex)
            {
            }
            finally
            {
                LogWriteLock.ExitWriteLock();
            }
        }

2.实时上传直接调用采集上传的方法,我这里直接调用服务端API方法实现。

   public void ActionRecordCollectAsync()
        {
            //调用服务接口,写入埋点信息到数据库中
        }

3.异步上传在需埋点的位置调用埋点的方法,将埋点数据写入本地文本文件中,以便后续的采集上传

    public void WriteMessage(string actionRecordJson)
        {
            try
            {
                LogWriteLock.EnterWriteLock();

                var query = (from f in Directory.GetFiles(file, "*.txt")

                             let fi = new FileInfo(f)

                             orderby fi.CreationTime descending

                             select fi.FullName).ToList();

                var logFilePath = query.FirstOrDefault();

                if (query.Count == 0)
                {
                    logFilePath = file + "\\" + DateTime.Now.ToString("yyyyMMdd-hhmm") + ".txt";
                }

                //判断文件是否被占用

                if (query.Count > 0 && FileStatusHelper.IsFileOccupied(logFilePath))
                {
                    logFilePath = file + "\\" + DateTime.Now.ToString("yyyyMMdd-hhmm") + ".txt";
                }

                using (FileStream fs = new FileStream(logFilePath, FileMode.OpenOrCreate, FileAccess.Write))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        sw.BaseStream.Seek(0, SeekOrigin.End);
                        sw.WriteLine(actionRecordJson);
                        sw.Flush();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                //退出写入模式,释放资源占用

                //注意:一次请求对应一次释放

                //      若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放]

                //      若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定]

                LogWriteLock.ExitWriteLock();
            }
        }

并发问题的解决(解决思路):

1.写入的时候,判断文件是否被占用,若被占用,重新创建一个文件写入

2.读取的时候,获取文件夹下所有文件,根据时间进行倒叙排序,读取之后上传数据库删除文件。

            public class FileStatusHelper{

            [DllImport("kernel32.dll")]

            public static extern IntPtr _lopen(string lpPathName, int iReadWrite);

            [DllImport("kernel32.dll")]

            public static extern bool CloseHandle(IntPtr hObject);

            public const int OF_READWRITE = 2;

            public const int OF_SHARE_DENY_NONE = 0x40;

            public static readonly IntPtr HFILE_ERROR = new IntPtr(-1);

            /// <summary>

            /// 查看文件是否被占用

            /// </summary>

            /// <param name="filePath"></param>

            /// <returns></returns>

            public static bool IsFileOccupied(string filePath)
            {
                IntPtr vHandle = _lopen(filePath, OF_READWRITE | OF_SHARE_DENY_NONE);

                CloseHandle(vHandle);

                return vHandle == HFILE_ERROR ? true : false;
            }

        }

        总结:暂时实现了自己所需的功能,但是因为自己对多线程的薄弱,所以实际并没有经过大量数据和并发情况的考验。大家有时间可以自己拿下来测试一下,欢迎大家反馈改进

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值