目录介绍
- 01.整体概述说明
- 1.1 项目背景
- 1.2 遇到问题
- 1.3 流程优化
- 1.4 设计目标
- 1.5 产生收益分析
- 02.日志上传设计思路
- 2.1 逆向推理
- 2.2 整体实践思路
- 2.3 关键技术选型
- 2.4 上报文件策略
- 03.一些关键技术点
- 3.1 平台日志触发
- 3.2 指令什么时候下发
- 3.3 客户端监听指令
- 3.4 指令未到达如何处理
- 3.5 本地日志上传过程
- 3.6 日志安全性考虑
- 3.7 日志分片上传实践
- 3.8 完善的日志链路
- 04.方案基础设计
- 4.1 整体架构图
- 4.2 UML设计图
- 4.3 关键流程图
- 4.4 接口设计图
- 4.5 模块间依赖关系
- 05.其他设计说明
- 5.1 性能设计
- 5.2 稳定性设计
- 5.3 灰度设计
- 5.4 降级设计
- 5.5 异常设计
- 06.其他说明介绍
- 6.1 遇到的坑说明
- 6.2 遗留问题
- 6.3 未来规划
- 6.4 参考链接
00.问题总结思考
- 日志上传:
- 日志上传的业务逻辑是什么样的?有什么收益?日志上传总体设计方案是什么样的?
01.整体概述
1.1 项目背景
- 哪类bug使我们比较头疼?个案、难复现、无可用信息……
- 当你终于解决了一个BUG的时候,然后你发现BUG只不过是变得更隐蔽了而已。如何才能解决这个烦恼,高可用平台中的日志回捞会给你答案~
1.2 遇到问题
- 线上运行场景复杂
- 设备碎片化严重、网络环境复杂、个性化配置丰富、用户操作难以预测。有时候很难复现提出的问题
- 线上Bug排查的痛点
- 线上四个场景组合起来就很容易出现各种个案问题排查这些问题就会有以下几个痛点:沟通反馈时效低、模拟场景难、问题难以复现、无可用信息
- 日志获取面临的问题
- 分析线上问题最需要的是日志信息,而在日志获取时,会面临以下问题:线上日志不全、被动获取时效低、日志全量上报耗流量耗资源、定向获取日志困难、协助成本大
1.3 流程优化
- 日志上传老的逻辑。这个过程很被动
- 运营人员通知用户 → 用户按照操作上传日志 → 上报成功 → 有权限童鞋下载 → 修复问题
- 这个过程中,客户联系用户上传日志比较耗时,而且可能遇到用户反感!
- 日志回捞流程优化。这个过程是主动
- 平台主动下发指令 → 客户端收到指令上报日志 → 上报成功 → 告知开发 → 成功支持一键下载 → 修复问题
1.4 设计目标
- 上传库目标
- 第一目标:由于上报日志比较复杂,单独弄成lib库,跟Logu库完全解耦合
- 第二目标:上报日志,涉及file文件上传限制,网络重试,分片上传等,因此暴露api供外部开发者配置
- 第三目标:上传功能灵活
- 上传库功能描述
- 0.彻底解耦合,方便拓展,将该上传功能抽离出来。能够在多个项目中应用
- 1.支持多种方式上传,默认是上传到网络
- 2.支持外部多配置属性,设置最大上传文件,文件路径,上传链接url,日志查询条件等。后期把逻辑调通后沉淀为库lib
- 3.支持重试,限制重试次数,思考文件过大则分片上传
- 4.服务端给客户端发送日志回捞指令,要支持启动后上传,也要支持切换到后台上传
- 5.关于上传结果回调,告知给外部开发者
- 6.由于logu日志是按照日期命名文件,因此在不改动该库条件下,先实现上传某日的日志
- 7.由于查询日志可能有多个文件,则需要找到符合条件日志,然后打包成zip包上传
- 8.外部设置最大zip包文件大小,超过大小,创建下一个zip包压缩然后上传【这个待定有点复杂】
- 9.支持上传小时级别日志文件【这个难度极大,首先logu并没有实现该功能】
1.5 产生收益分析
- 日志回捞变成主动
- 方便开发主动去回捞日志,而不需要通过中间层层联系用户。省时省力
- 该库具有通用性
- 该库内聚力强,拓展性强,接入性强。可以快速且无成本移植到其他的项目中
- 特点说明
- 方便接入,定向回捞,个案排查,即时获取,简单容易用
02.日志上传设计思路
2.1 逆向推理
- 逆向推理的过程
- 遇到了某个问题,先去后台创建一个任务(任务可以设置拉取时间范围,任务设备或者用户id或者手机号)
- 客户端收到服务端指令开始上传数据
- 数据上传平台成功,开发可以下载,开始分析问题
2.2 整体实践思路
- 第一步:开发创建任务后,服务端要告诉客户端。这一步服务端开发开发
- 1.服务端给客户端发消息,怎么发?2.消息中带有哪些信息?
- 客户端主动请求,发送push;消息中带有:时间段
- 第二步:App收到指令上传日志。这一步客户端开发
- 1.App何时上传日志 ?2.上传日志怎么截取时间段?
- 重新启动,或者推到后台,上报日志
- 第三步:平台显示状态。这一步日志平台和服务端开发
- 1.平台怎么显示不同状态?建议服务端给一个上报日志状态的接口,可以查询状态。
2.3 关键技术选型
- 关于日志上报,因为涉及到搜索条件日志,压缩zip包,上传网络。因此涉及到异步线程……
- 关于使用异步任务技术有多种,分别如下所示
- 第一种:Thread直接创建,在子线程中做搜索条件日志和压缩zip包,但不建议直接使用线程创建
- 第二种:AsyncTask,里面封装线程池管理线程,适合后台任务交互,有可能被其他进程的任务抢占而降低效率
- 第三种:HandlerThread,内部实现了普通线程的 Looper 消息循环。可以串行执行多个任务,一般用于轮训操作
- 第四种:IntentService,是一种异步、会自动停止的服务。一般用于下载,上传等操作挺合适
- 第五种:线程池Executors,创建线程池用于日志匹配和压缩zip包,也可以实现该功能
- 最后选择第四种IntentService
- 多次调用即执行多次任务,每次任务处理完即自动销毁。内部使用HandlerThread实现,不会阻塞主线程,可以处理后台任务,具有service优先级。
2.4 上报文件策略
- 根据日期选择要压缩的日志文件,压缩包限制默认为100m(可配置),过大删除,上传成功后清除本地压缩包。
03.一些关键技术点
3.1 平台日志触发
- 平台搜索日志的纬度
- 可以按照手机号或者用户id搜索
- 搜索日志的条件有哪些
- 搜索日期,开发输入特定日期后,可以过滤客户端本地符合条件
- 或者全部日志
3.2 指令什么时候下发
- 实时通知是通过push/tcp的方式实现的
- 服务端通过手机号来区分调用不同业务的push后端,然后通过手机号查到单个用户的token,精准的定向下发,push到用户的设备上。
3.3 客户端监听指令
- 客户端监听指令
- 服务端发送的指令:TcpPacket(privateTag = 123_1655700134223,length = 46,version = 1,type = GENERAL_PROTOCOL(123),data:{"device_id":280203,"data":{},"sub_type":2008})
- 由此可以确定两点:解析packet消息体,通过type=GENERAL_PROTOCOL判断是通用协议,然后再取data中数据,拿到data中sub_type为2008即为日志回捞指令
- 客户端上报时机。目前客户端选用第一种方案
- 第一种:接到指令后,即开始上报。记录收到指令的信息
- 第二种:接到指令后,用户切换到后台,即开始上报
- 回捞上报分为push触发和APP启动上报
- Push触发主要是为了及时性,但由于push的触达率、过期等问题,会导致收不到,对应的兜底方案就是APP启动上报。主动上报是用户主动触发或客服引导用户触发。
3.4 指令未到达如何处理
- 若在有效期内,用户的APP未打开过,则push可能在有效期内会失效。
- 在此情况下,如何保证任务仍能够将日志回捞?在客户端启动时,通过指令的方式,在客户端做一个主动请求,将userID携带过来,通过userID,服务端会在任务表中查这个userID是否有对应的任务。若有,返回任务信息,并返回给客户端,任务上报的指令。
3.5 本地日志上传过程
- 大概流程如下所示
- 第一步:首先拿到源log日志文件,判断文件是否存在。如果存在走下一步
- 第二步:按照服务端指令,搜索特定条件日志(如果没有条件那就选全部日志),将匹配日志放到集合中
- 第三步:将符合条件日志文件,压缩到zip包
- 第四步:将zip包,上传到网络指定的接口(包括接口和公共参数拼接),有错误重试机制
- 第五步: 上传成功后,删除本地zip文件
3.6 日志安全性考虑
- 整个日志下载鉴权的过程主要是为了保证日志下载地址能够进行校验,不会被泄露。目前主流方案有:
- 第一种:对zip进行加密,设置密码,相当于下载了zip日志文件后解压缩需要输出密码
- 第二种:服务端生成的链接具有时效性,比如30分钟后就过期了
- 第三种:不做zip加密和解密处理,因为在平台上捞日志一般都是公司内部人员(需要平台账号)
- 关于写日志注意点
- 不可以记录一些用户私密数据到本地,因为日志文件是写到外部存储的data/包名目录下,还是有一种危险性……
3.7 日志分片上传实践
- 分片上传主要针对zip包比较大
- 7天的所有日志,比较大,大到一定阀值后采用分片上传
- 分片上传的整体思路点
- 第一步:先分块,把块分好!总大小/每一块的值。
- 第二步:使用md5校验每一块完整性。
- 第三步:多并发处理网络请求,这个并行可以用CountDownLatch来监听结果
- 第四步:进度计算(已上传的块数*每个块数大小)
- 第五步:结束的判断!怎么知道全部上传完成了?判断所有的块都上传成功了
- 第六步:重试机制,中间某个异常需要重试
- 第七步:取消上传,有回调吗?是不是应该给取消接口?
- 第八步:进程不在,怎么知道失败的个数和数量?
3.8 完善的日志链路
- 如下所示
2022-06-21 10:54:25.674 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: UploadService: search format time is : 2022-06-21 2022-06-21 10:54:25.674 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: UploadService: zip file name : ty2022062110542567.zip 2022-06-21 10:54:25.674 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: UploadService: zip file path : /storage/emulated/0/Android/data/com.zuoyebang.iotunion/files/log/app/ty2022062110542567.zip 2022-06-21 10:54:25.819 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: UploadService: write file to zip , total time : 145 2022-06-21 10:54:25.819 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: UploadService: zip write state : true 2022-06-21 10:54:25.821 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: RequestManager: post file , if error , try upload 2022-06-21 10:54:25.821 15427-17745/com.zuoyebang.iotunion I/UploadHelper:: RequestManager: try upload file 0 2022-06-21 10:54:25.824 15427-17746/com.zuoyebang.iotunion I/UploadHelper:: Interceptor: CommonParams intercept:POST,oldBody = oldRequest.body:okhttp3.MultipartBody@1f2ff6b 2022-06-21 10:54:25.824 15427-15427/com.zuoyebang.iotunion I/UploadHelper:: UploadService: service destroy 2022-06-21 10:54:25.825 15427-17746/com.zuoyebang.iotunion I/UploadHelper:: Interceptor: finalQueryParams:{sig=67e186aa46808d5afa4055e4ffecd76c, rom=SAMSUNG, app_version=2.3.0, stamp=1655779739452, brand=samsung, phone_model=SM-A5160, token=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI4MDIwMywianRpIjoiZjU2NGUwOTA3ODUyNGMwNTgwMDNiMWJlOTM3Y2U1OWIiLCJpYXQiOjE2NTU3NzY3NzksInN1YiI6IjI4MDIwMyJ9.QdQIqUM1pz37uBihcFb4UKz4okVAstL5XIOVtGtItHs} 2022-06-21 10:54:26.262 15427-17746/com.zuoyebang.iotunion I/UploadHelper:: RequestManager: net post file success 200 2022-06-21 10:54:26.262 15427-17746/com.zuoyebang.iotunion I/UploadHelper:: UploadService: zip file upload success 2022-06-21 10:54:26.263 15427-17746/com.zuoyebang.iotunion I/UploadHelper:: UploadService: zip file del success
04.方案基础设计
4.1 整体架构图
- 整体架构流程图
image image
4.2 UML设计图
4.3 关键流程图
- 日志回捞流程图
image
4.4 接口设计图
- 上报文件接口图
image
- 稳定性接口图
image
4.5 模块间依赖关系
05.其他设计说明
5.1 性能设计
5.2 稳定性设计
- 瓶颈:客户端收到指令消息,受一些条件影响:
- Android需要确保进程在后台存活;网络环境太差,Android上tcp长连建立可能不成功。
5.3 灰度设计
5.4 降级设计
- 针对该库,配置了异常降级,降级之后将影响功能使用。外部具体如何操作,如下所示:
//设置AB降级策略 .setMonitorToggle(object : IMonitorToggle { //todo 是否降级,如果降级,则不使用该功能。留给AB测试开关 override val isOpen: Boolean get() = false })
5.5 异常设计
- 比如接入日志上报库。该库出现了异常被捕获了,影响了功能使用。如何让壳工程代码统计到sdk中的捕获异常?
//将sdk内部异常上报到Apm平台上 .setExceptionTrack(object : IExceptionTrack{ override fun onException(e: Exception) { LogUtils.d("UploadHelper: setExceptionTrack $e") ApmHolder.crash?.reportCustomErr("uploadLogException", ApmHolder.CatchExceptionType,e) } })
- 这里日志库因为涉及到zip压缩过程,这个过程有io流操作,因此将捕获的异常通过接口暴露给外部开发者,虽然这个概率非常非常低……
5.6 事件统计设计
- 因为日志上传涉及到网络请求,因此可能会有请求异常。那么此时需要将一些异常统计到,暴露给外部接口。外部具体如何操作,如下所示
//设置event事件上报策略 .setEventTrack(object : IEventTrack { override fun onEvent(name: String, map: HashMap<String, String>) { //todo 业务埋点事件 LogUtils.d("UploadHelper: $name : $map") } })
- 目前该库哪里记录了异常事件。具体有重试结束统计重试的次数;网络请求失败统计失败的原因;网络请求成功但业务失败统计服务端给的code码等
06.其他说明介绍
6.1 遇到的坑说明
6.2 遗留问题
- 目前服务端日志回捞,平台无法选择特定日期,也就是按照选择日期上传
- 这个让服务端添加
- 目前日志回捞,App必须在线才能接收到TCP消息指令
- 这个让服务端增加一个查询日志接口
- 由于tcp的触达率等问题,进程杀死等,会导致收不到,对应的兜底方案就是APP启动上报。
6.3 未来规划
- 目前日志主动回捞,仅仅在碳氧App上使用,还在支持的场景还比较单一。
- 规划1: 未来可以做到增加日志过滤,,设置日志logu库过期时间长一些。
- 规划2: zip压缩包限制最大大小。不仅仅可以用来上报日志,还可以上报其他文件(图片,json,缓存数据等)
- 规划3: 分片上传文件,目前7天日志文件比较小,一般就几兆,因此大多数情况下直接上传就成功了。增加分片上传
6.4 参考链接
- https://blog.csdn.net/weixin_36051633/article/details/112622700
- 58 移动端日志回捞探索实践
- https://toutiao.io/posts/nzg8is/preview
- 京东零售云mPaaS移动端日志回捞探索实践
- https://www.51cto.com/article/682747.html
- okhttp带进度上传
- https://www.jianshu.com/p/bf734b3fb304
- APP系统报错日志反馈机制设计
- https://blog.csdn.net/RuihanChen/article/details/51133742