敞开成长之旅!这是我参加「日新计划 12 月更文挑战」的第19天,点击检查活动概况

布景

一条工单引发的日志需求 咱们的业务收到了一个信息走漏的工单,缝隙是明文打印日志。

咱们当时运用的是某网络库自带的日志体系(以下简称 A 体系),会直接将明文写入到了日志文件傍边,这样就有信息走漏的危险。 咱们搜集了当时运用 A 体系的痛点:

  1. 明文打印,形成隐私走漏;
  2. 关键日志丢掉;
  3. 无法便利的打印当时的仓库,线程等信息;
  4. 不支撑各种格式化输出。

为了处理这个问题,咱们规划并完结了 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,私钥存在服务端,可以确保安全,可是功能不太好。
因而咱们综合了一下,运用了混合加密的办法,既能确保安全又能统筹功能,详细的流程如下图所示:
安全高效可视化的日志解决方案
  1. 客户端和服务端先要约定一套公钥和私钥;
  2. 当客户端创立日志块时,随机生成一串字符串 key,此刻运用公钥加密随机生成的 key 得到 key’,将 key’ 记录到日志文件傍边,然后 key 当作对称加密的密钥来加密日志;
  3. 服务端在解密时,需求先将 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 供给了本地解密东西,咱们只需求将日志导出,用本地解密东西进行解密。本地解密东西只是将大部分解密的过程放在本地,中心的非对称解密过程依然放在服务端,不会形成密钥的走漏。

安全高效可视化的日志解决方案