敞开成长之旅!这是我参加「日新计划 12 月更文挑战」的第19天,点击检查活动概况
布景
一条工单引发的日志需求 咱们的业务收到了一个信息走漏的工单,缝隙是明文打印日志。
咱们当时运用的是某网络库自带的日志体系(以下简称 A 体系),会直接将明文写入到了日志文件傍边,这样就有信息走漏的危险。 咱们搜集了当时运用 A 体系的痛点:
- 明文打印,形成隐私走漏;
- 关键日志丢掉;
- 无法便利的打印当时的仓库,线程等信息;
- 不支撑各种格式化输出。
为了处理这个问题,咱们规划并完结了 TinyLog。 TinyLog 能支撑日志的加密和紧缩,削减日志文件大小并确保隐私不会走漏;可以支撑各种格式化输出,且比较便利的打印出当时的仓库,线程等信息;咱们在规划的时分也考虑到了日志丢掉的问题,在 crash 的场景,也可以确保当时的 crash 仓库可以被记录下来;此外咱们还供给了图形化展现日志的接口,可以将日志图形化的展现出来,便利开发同学快速的定位到问题。
TinyLog 的整体规划
作为一个日志组件,安全和高效是咱们的一个中心需求。安全包括两点,日志不丢掉以及隐私不走漏;高效就是日志组件不能影响运用的功能。咱们在规划 TinyLog 的时分也充分考虑到了这两点,以下是 TinyLog 的整体规划图: 客户端加密,服务端解密。服务端供给明文下载以及图形化展现日志的功能。客户端来确保打印日志的高效性,服务端来确保安全性。
1.TinyLog SDK
如何才能支撑多种格式化输出,并能坚持杰出的功能呢? 以下是 TinyLog SDK 的结构图: 咱们将 TinyLog SDK 分为三层,别离是输入层,处理层和输出层。其间处理层包括了许多日志的装备项,可以满意各种场景的格式化输出;别的,在输出层,咱们将加密,紧缩以及文件写入的功能下沉到 c++,确保了杰出的功能。
输入层: 输入层为对外供给的接口,首要供给了初始化办法以及日志打印的办法。咱们参考了体系打印日志的接口,并在其基础上进行扩大,下降业务方的接入本钱。以下为 TinyLog 和体系日志打印的接口比照,以 info 等级为例:
体系原有的两个日志打印的接口,咱们将其扩大为4个,并在其基础上进行了增强。如体系接口 i(String tag,String msg),第二个参数只能是字符串目标,而咱们将其增强为 i(String tag,T obj),第二个参数可以是任何目标,咱们会匹配对应的格式化办法,将其进行格式化处理。比方第二个参数是 json 目标,咱们就会运用 JsonFormatter 将 json 目标进行格式化,若输入的目标在 TinyLog 内部未匹配到任何 Formatter ,则会直接调用该目标的 toString 办法。
处理层: 在这一层,日志会被分发给 Logger,每个 Logger 都包括一个 LogConfig(装备信息)和 Printer(输出), Logger 会根据 LogConfig 将日志封装成为一个日志目标,然后传递给 Printer 输出。LogConfig 的装备信息包括日志等级过滤,是否打印线程,是否打印边框等,以及各种格式化办法。 咱们将格式化笼统成了一个 Formatter 接口,并默许完结了各种场景的格式化办法,详细如下表格所示,业务方也可根据需求自己完结 Formatter 接口。
格式化 | 备注 |
---|---|
DefaultBorderFormatter | 边框格式化 |
DefaultJsonArrayFormatter | Json数组格式化 |
DefaultJsonFormatter | Json目标格式化 |
DefaultStackTraceFormatter | 仓库格式化 |
DefaultThreadFormatter | 线程格式化 |
DefaultThrowableFormatter | 反常信息格式化 |
DefaultXmlFormatter | xml格式化 |
输出层: 咱们将输出笼统成了 Printer 接口,TinyLog 默许完结了 ConsolePrinter 和 FilePrinter,别离将日志输出到控制台和文件傍边。若输出到文件,则可以将日志上传到服务端。业务方也可以自定义 Printer,将日志输出到任何地方。
让日志的输出愈加友好
TinyLog 可以很轻松的打印当时的线程信息,仓库信息以及常见的目标,并默许完结了各种格式化办法,包括边框,json,线程,仓库等。 例如,以下是打印一个 json 目标,别离运用体系 Log 和 TinyLog 的比照:
防止日志丢掉
如何防止日志丢掉是一个非常重要的问题,咱们最初运用 A 体系的时分,经常有同事抱怨关键信息丢掉。因而在规划 TinyLog 的时分,咱们也考虑到了这个问题。
(1)运用 mmap
咱们分析了 A 体系,发现其日志打印的流程如下图左面所示: 这样的结构就会有日志丢掉的危险,由于运用了内存缓存,一旦发生 crash,缓存傍边的日志就丢掉了。 怎样才能防止日志丢掉呢?最好的办法就是打印每条日志的时分马上写进入文件,可是这样就会频频的触发 IO,存在功能的问题。经过调研之后,咱们运用 mmap 的办法,既可以防止日志丢掉,又能确保功能,如下图右边所示。
为什么运用 mmap 功能又好,还不会形成数据丢掉呢?以下是标准 IO 和 mmap 在写场景的比照图,标准 IO 下,当咱们需求将写入的数据从用户空间拷贝到内核空间,然后体系会定时的将数据写入磁盘;而运用 mmap,用户空间和内核空间经过映射同一个一般文件完结内存同享,咱们在用户空间内写入的数据相当于直接写入到了内核空间,削减了一次内存拷贝,功能得到提升。而且一旦数据写入到了内核空间,此刻 app 即便发生了 crash 也不会形成数据的丢掉,依然可以由体系确保写入到磁盘上。
以下是我做的一个比照试验,把50 byte 的日志别离写入内存,mmap和文件 10w 次,以下是试验结果:
机器 | 写入内存 | 写入mmap | 写入文件 |
---|---|---|---|
三星(s20) | 16ms | 32ms | 815ms |
iphone 6s | 4.9ms | 4.3ms | 987ms |
可以看出,mmap 在功能上挨近于内存。 |
(2)反常捕获
Android 版本 sdk 咱们在 java 层做了全局的反常捕获,当 TinyLog 监测到反常发生时,会马上将当时的反常信息仓库写入到日志傍边,当日志写完之后才开释反常。以此确保当 crash 发生时,可以将 crash 的仓库信息保留下来。 ios 版本 sdk 需求依赖 bugly 或其他溃散收集组件,在其 crash 回调函数中调用 TinyLog 接口写入 crash 仓库。
安全统筹功能
加密算法一般分为对称加密和非对称加密,以下为各自的特色:
分类 | 特色 | 速度 |
---|---|---|
对称加密 | 加密和解密的密钥是相同的 | 快 |
非对称加密 | 公钥加密,私钥解密 | 慢 |
对于日志 sdk 这个场景,若选用对称加密,则必须要将密钥放到 sdk 傍边,才可以完结日志文件的加密,这样会有密钥走漏的危险; | ||
若选用非对称加密,公钥存入 sdk,私钥存在服务端,可以确保安全,可是功能不太好。 | ||
因而咱们综合了一下,运用了混合加密的办法,既能确保安全又能统筹功能,详细的流程如下图所示: | ||
- 客户端和服务端先要约定一套公钥和私钥;
- 当客户端创立日志块时,随机生成一串字符串 key,此刻运用公钥加密随机生成的 key 得到 key’,将 key’ 记录到日志文件傍边,然后 key 当作对称加密的密钥来加密日志;
- 服务端在解密时,需求先将 key’ 从日志文件傍边读出,然后运用私钥解密出 key,再运用对称加密的办法解密日志得到明文。
虽然非对称加密办法比较慢,但在混合加密的场景只要在创立日志块的时分才会运用一次,而真实加密日志的办法运用的是功能较好的对称加密,每个日志块的密钥均为随机生成,而真实记录在日志文件傍边的密钥为加密过后的密文。因而在功能上挨近对称加密,而安全性又挨近于非对称加密。 以下为加密 1w 条 100 byte 日志的比照试验,运用的是三星 s20:
分类 | 时刻 |
---|---|
非对称加密 | 1070ms |
对称加密 | 33ms |
混合加密 | 50ms |
图形化显现日志接口
如何让日志愈加明晰和直观的展现? 现在咱们团队的开发同学运用 fancylog 的插件来检查日志,fancylog 是一个可以可视化显现日志的插件,其原理是根据装备好的正则表达式规则,将给定的日志文件图形化的显现出来,可以极大的提高检查日志的功率。
已然是经过正则表达式来匹配,那咱们何不将一些通用的办法笼统出来形成接口,然后为这些接口供给对应的正则表达式,这样只要运用这个接口打印出来的日志,就可以被 fancylog 解析出来,而无需自己装备了。 以下为 TinyLog 傍边供给的图形化显现日志的接口:
类别 | 接口 | 备注 |
---|---|---|
场景 | loadScene(String sceneName) | 加载场景 |
loadSceneSuccess(String sceneName) | 加载成功 | |
loadSceneFailed(String sceneName) | 加载失败 | |
操作 | switchFront(String msg) | 切前台 |
switchBack(String msg) | 切后台 | |
click(String msg) | 点击事情 | |
反常 | exception(String msg,Exception exception) | 出现反常 |
crash(String msg,Exception exception) | crash | |
进程 | processStart(String msg) | 开始 |
processing(String msg) | 进行中 | |
processEnd(String msg) | 结束 | |
其他 | event(String event) | 自定义event |
当咱们的日志上传到日志网站之后就可以主动的图形化展现了,如下图所示
非侵入式的打印日志
有同事提出,现在 TinyLog 所供给的图形化展现接口,是一种侵入式的结构化日志完结办法,业务方需求在代码中显现的调用 fancylog,才可以完结日志的结构化展现。已然这样咱们何不运用注解的办法,完结无侵入式的打印日志呢。 因而,咱们规划并完结了 FancyLogger 组件,咱们自定义了一些日志的注解,并针对被注解符号的办法添加切面,当对应的办法执行完结时,便触发日志打印的逻辑。 以下为 FancyLogger 傍边包括的注解:
注解 | 备注 |
---|---|
ActivityCycle | Activity生命周期,类注解 |
FragmentCycle | Fragment生命周期,类注解 |
Click | 点击事情,办法注解 |
Crash | crash,办法注解 |
Event | 自定义事情,办法注解 |
ExceptionLog | 反常,办法注解 |
LoadScene | 加载场景,办法注解 |
ProcessLog | 进程办法注解 |
Switch | 切前后台,办法注解 |
FancyLogger 傍边供给了两种注解,别离是办法注解和类注解,其间类注解为 ActivityCycle 和 FragmentCycle 可以主动的打印 Activity 和 Fragment 的生命周期。 以下别离为办法注解和类注解的示例。
办法注解
如下所示,在 testEvent 办法上面添加 Click 注解
@Click(msg = "testEvent")
public void testEvent(){
Log.i("测验","执行了testEvent");
}
当咱们执行 testEvent 办法时,会有如下打印,其间第一条日志为执行 testEvent 办法内部的日志,第二条日志为 FancyLogger 打印的日志。
类注解
如下所示,在 MainActivity 类上添加 ActivityCycle 注解
@ActivityCycle(tag="MainActivity")
public class MainActivity extends AppCompatActivity {
...
}
当 Activity 执行了对应的生命周期的办法时,会有如下日志打印:
TinyLog 的功能测验
以下为瞬间写入 10w 条 50byte 日志信息在各个不同维度的数据,其间内存,cpu峰值均运用 PerfDog 进行测验。
时刻
机器 | 不紧缩不加密 | 紧缩加密 |
---|---|---|
三星s20 | 269.2ms | 276.6ms |
iphone11 | 604.9ms | 815.8ms |
日志文件大小
机器 | 不紧缩不加密 | 紧缩加密 |
---|---|---|
三星s20 | 12.3MB | 851KB |
iphone11 | 10.3MB | 904KB |
内存占用
以下场景运用 TinyLog demo 进行测验。 三星s20
场景 | 不紧缩不加密 | 紧缩加密 |
---|---|---|
翻开运用 | 60MB | 63MB |
初始化 TinyLog | 63MB | 66MB |
写入10w条日志峰值 | 83MB | 91MB |
写入10w条之后的内存 | 67MB | 70MB |
iphone11
场景 | 不紧缩不加密 | 紧缩加密 |
---|---|---|
翻开运用 | 38MB | 38MB |
初始化 TinyLog | 38.2MB | 38.3MB |
写入10w条日志峰值 | 38.7MB | 39.8MB |
写入10w条之后的内存 | 38.6MB | 39MB |
cpu 峰值
机器 | 不紧缩不加密 | 紧缩加密 |
---|---|---|
三星s20 | 18% | 18% |
iphone11 | 10% | 10% |
2.TinyLog 服务端
日志多维度查找
TinyLog 上传日志时分,支撑自定义 tag。例如,用户反馈的场景可以添加 type 为 user,crash 场景可以添加 type 为 crash。咱们除了可以按照日志上传时刻进行查找之外,还可以根据自定义的 tag 进行查找。这样咱们就可以根据不同的 tag 愈加精准的定位到日志。
一键图形化展现日志
日志上传到服务端之后,点击在线检查,便可以将日志图形化展现出来。如下图所示,咱们经过右边的图形就可以快速的定位到问题。点击右边赤色的菱形模块,就可以定位到左面日志。
3.本地解压解密东西
假如咱们遇到一些特殊场景,不便利上传日志,TinyLog 供给了本地解密东西,咱们只需求将日志导出,用本地解密东西进行解密。本地解密东西只是将大部分解密的过程放在本地,中心的非对称解密过程依然放在服务端,不会形成密钥的走漏。