日志系统用于记录用户行为和数据以及崩溃时的线程调用栈,以帮助程序员解决问题,优化用户体验。
iOS系统就有自带Crash收集应用程序“ReportCrash”来收集App Crash信息,我也深入了解过iOS收集Crash 信息的过程并记录在此 CPU发生异常到生成Crash Log的过程 , 但用户遇到的很多问题不仅仅是Crash,更何况有些情况仅靠Crash Log并不能定位Crash,而且ReportCrash 收集的Crash信息还需要用户同意才可以和开发者共享。为了说明用户日志的重要性,这里引入一个faceu 团队-轻颜相机-Tom哥的调侃
“譬如用户反馈,拍照偏黄,中间经过了十几个渲染管线,没有log真呵呵,你又不可能让用户再拍一次”
因此大多数App 都带有Crash收集框架和日志收集框架,而Crash信息也是日志信息的一种,为什么要分成两个框架去收集呢?因为信息采集方式不一样,Crash收集框架通过捕获系统发送来的 Mach 异常和 Unix 信号进行信息采集、而日志收集框架是程序员主动代码触发的信息采集,信息采集部分共用代码很少,所以分成两个框架也更易于维护。
这里介绍的日志系统是收集非Crash 信息的日志系统。该系统分为三部分:
下面和大家聊一聊我技术选型的过程
都说技术服务业务,考虑技术选型当然离不开业务需求。
其实团队项目之前就有日志系统,没有使用三方框架,而是自己写了一些简单的策略,如按优先级写文件或上报服务器。但仍满足不了客户端开发的需求
开发有时间限制,需求也就有优先级之分
服务端和前端同学人力紧张,而且这个不是挣钱的产品需求,不一定能争取到排期让服务端和前端同学支持“稳定的日志传输服务和可视化的日志管理”这个需求,而且我们的Crash 收集和查看工具都是用的腾讯的,不是同一个公司同一个部门,更别指望他们来支持了,只能网上寻找解决方案。
本人服务端开发经验匮乏,只搞过vps搭建vpn和博客,并没有玩过web服务器,找到一个不需要搭建web服务器的方案。服务器搭建Seafile 个人网盘服务
Seafile,是一套中国国产的开源、专业、可靠的云存储项目管理软件,解决文件集中存储、共享和跨平台访问等问题。正式发布于2012年10月。除了一般网盘所提供的云存储以及共享功能外,Seafile还提供消息通信、群组讨论等辅助功能,帮助员工更好的围绕文件展开协同工作,已有10多万用户使用。
Seafile 是比较成熟的方案了,还提供接口来上传、下载文件,赶紧买个10块/月的腾讯云学生机搭个Seafile 个人网盘验证一下。
半天业余时间就解决了“稳定的日志传输服务和可视化的日志管理“,接着又调研了下客户端日志采集框架,发现 CocoaLumberjack start数量很多,最近还有提交记录,而且框架设计得易于扩展,已经有基于CocoaLumberjack 去解决“有通过web socket实时输出log到web页面”问题的方案,另外,CocoaLumberjack 还支持对文件数量、单个文件大小的设置。CocoaLumberjack 这个方案看着挺好,马上又撸了个Demo去验证。
拿着Demo去和同事讨论,总结了几个问题:
Seafile,高定制化牺牲了可扩展性
CocoaLumberjack 仍无法百分百保证Log的 完整性 ,在App Crash时无法保证Log 已经写到文件。因为CocoaLumberjack是通过 -[NSFileHandle writeData:] 来进行写文件的,此方式除了无法保证Log完整性外,相对mmap中使用内存映射文件来执行写操作的方案也较慢
既然找不到服务端和前端同学来帮忙,那就硬着头皮自己上吧。分解任务,逐个击破!将日志系统分成相对独立的三部分 采集部分、传输部分、管理部分
在采集部分,之前一直没有提到 流畅性 的重要程度,其实这个才是日志采集组件的最高优先级需求,不能因为日志频繁写文件导致应用程序卡顿或耗电量增加。因为项目中之前没有高频高密度地使用日志,没能及早意识到 流畅性 的重要程度。频繁写文件为什么会卡顿?
当写文件的时候,并不是把数据直接写入了磁盘,而是先把数据写入到系统的缓存(dirty page)中,系统一般会在下面几种情况把 dirty page 写入到磁盘:
而且数据从程序写入到磁盘的过程中,牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。其中内核空间和用户空间频繁切换的话也带来性能损耗。
避免频繁写文件,先在内存中创建buffer,合适时在进行写文件。这个方式虽然保证了流畅性,缺无法保证完整性,而且集中压缩日志会导致 CPU 短时间飙高。程序发生Crash的话内存中的数据还没有持久化,实时写文件的话又无法保证流畅性,该如何是好?
mmap 是使用逻辑内存对磁盘文件进行映射,中间只是进行映射没有任何拷贝操作,避免了写文件的数据拷贝。操作内存就相当于在操作文件,避免了内核空间和用户空间的频繁切换。
为了验证 mmap 是否真的有直接写内存的效率,微信团队写了一个简单的测试用例:把512 Byte的数据分别写入150 kb大小的内存和 mmap,以及磁盘文件100w次并统计耗时
mmap 除了能保证 流畅性 ,还能兼顾日志的 完整性 ,下面这些情况回自动回写磁盘
msync(mmap_ptr, mmap_size, MS_ASYNC) munmap(mmap_ptr, mmap_size)
xlog 还保证了安全性和容错性
通过压缩和加密可以保证日志信息非明文写入磁盘,同时减少所占用的 mmap 的大小。策略是
在写进逻辑内存之前就把日志先进行压缩,再进行加密,最后再写入到逻辑内存中
微信选择的具体压缩方案可以参考文章 cloud.tencent.com/developer/a… ,简单来说就是 能够保证日志 容错性 的流式压缩,
即使压缩单位中有部分数据损坏,因为是流式压缩,并不影响这个单位中损坏数据之前的日志的解压,只会影响这个单位中这个损坏数据之后的日志。
所以一句话总结xlog 为什么具有 安全性 、 流畅性 、 完整性 、 容错性 的优点
使用流式压缩方式对单行日志进行压缩,压缩加密后写进作为 log 中间 buffer的 mmap 中,当 mmap 中的数据到达一定大小后再写进磁盘文件中
另外mmap 相关的API 如下,参考开源的XLog,你也可以给团队定制基于mmap的日志采集组件
FILE *fp = fopen(file_path, "wb+"); file_num = fileno(fp); ftruncate(file_num, size); // 调整size char *mmap_ptr = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, file_num, 0); // 然后就可以对 mmap_ptr 进行读写了 munmap(mmap_ptr, mmap_size); // 解除一个map,内容会写回磁盘 msync(mmap_ptr, mmap_size, MS_ASYNC); // 同步,异步写回磁盘 扩容办法:先解除munmap,调大文件大小,重新调 mmap 映射即可 复制代码
手淘的SatanWoo 五子棋大佬在解析xlog源码的时候给出了两点使用mmap时需要额外注意的点,文章在此
在网上有很多介绍Spring Boot的文章,蚂蚁金服的一位前辈写了篇 Spring Boot快速开始指南 我看还不错,介绍了Spring-Boot的知识点线路图和基本概念,还有如何快速创建一个Spring Boot 应用。
我在自己的学生机开发完,用postman 调试完接口再去找公司运维要机器资源,部署到了公司机器上。由于我也是照葫芦画瓢,只是使用Spring Boot提供了简单的文件上传和下载功能,暂时无法在这一块深入介绍自己的经验,不过日志系统对应的Spring Boot部分我已经放到了github,感兴趣可以clone 下来跑跑看 github.com/HonchWong/H…
项目名字是RDA,也就是研发助手的英文,意味着这个Spring-Boot应用不会止步于此。因为平时iOS业务开发中会经常遇到些阻碍效率的问题,会想出很多“牛逼”的技术方案去解决,但仅仅熟悉Cocoa 框架是实现不了的,少不了服务端的支持,比如最近业余在做的三个需求,【客户端可视化Mock数据:提供可视化的界面去设置Mock网络数据,无需硬编码Mock和减少编译时间】、【客户端可视化一键生成bug单:提供可视化的界面去输入bug描述,并生成bug单,提高研发效率】、【客户端埋点管理平台:提供平台去管理埋点需求、验证埋点、埋点信息自定义展示】,这也是立个flag,技术服务业务,业务必将反哺于技术,19年好好学习服务端知识,再来完善这篇文章 :)
Ant Design Pro 是蚂蚁金服团队在 Ant Design 的设计规范与组件库基础上推出的一套 React 实现的企业级中后台前端/设计解决方案。基于这个框架可以快速开发后台管理系统,如果你有React Native的开发经验,那么对基于React开发的Ant Design Pro 肯定也是上手很快。本日志系统使用到的 Ant Design Pro 放到了github github.com/HonchWong/H…
除了服务端和前端的Demo,iOS端的Demo我已经提交到github。需要从github下载两个工程 web服务器工程 github.com/HonchWong/H… 、 iOS端工程 github.com/HonchWong/H…
userlog.dir.path=/Users/huanghongchang/Desktop/userLog
初始化xlog。 涉及四个API,具体调用可以参考Demo工程中的 XLogHelper.m +setupXlog
setxattr([[self xlogFileDirPath] UTF8String], attrName, &attrValue, sizeof(attrValue), 0, 0); xlogger_SetLevel(kLevelDebug); appender_set_console_log(true); appender_open(kAppednerAsync, [logPath UTF8String], "Test");
打log, xlogger_Write(&info, message.UTF8String)
,Demo用宏定义封装了该API
需要在APP终止方法applicationWillTerminate中反初始化 appender_close()
以上只是Demo中的使用方式,更多详细API 可以参考xlog的wiki Mars iOS/OS X 接口详细说明 ,Demo中对log日志进行加密,appender_open 提供了参数传入公钥,xlog的加密使用可以参考文档 Xlog 加密使用指引
本日志系统日志上传的策略采用白名单的方式,在后台管理系统设置白名单,白名单包含用户唯一标识符uin及对应的上传的日志类型(如数据库or日志log),用户输入白名单中的uin,获取需要上传的日志类型进行日志上传。
在HCRDA-SpringBoot 目录下执行 mvn spring-boot:run 后,在浏览器中访问 http://localhost:9080 , 输入账户admin 密码 123,在用户日志-指定用户中可以看到有白名单中已经有八个,目前Demo 中只实现了【选择日志上传单个文件】和【上传全部日志】
设置白名单后便可在Demo 中走上传流程,操作步骤如下
在后台管理系统中可以查看和下载上传的日志
我这里并没有对xlog文件进行加密,而是对zip文件进行了加密,而密码用了xor的方式对字符串进行混淆避免用hopper等反汇编工具直接查看,当然这个还不是绝对安全,还是可以通过逆向App,动态调试,获取zip文件的密码,只不过也加大了破解的难度。虽然没有加密,但单行log还是进行了流压缩,需要用这个脚本进行解压 decode_mars_nocrypt_log_file
执行脚本 python decode_mars_nocrypt_log_file.py xxx.xlog