Android 开源:日志记录工具 TextRecorder

前言

之前在项目中将一些日志内容保存到sd卡文件的时候,发现公司一直使用的是Util.save(String tag, String text)形式来记录的,不同的文件名或文件目录采用tag进行区分,文件内容为text,写入逻辑为:1、打开文件;2、写入内容;3、关闭文件;

但这样的逻辑存在以下两个主要问题:

1、如果需要保存多条内容就执行多次save()方法,且直接在当前线程执行,这带来的一个明显的问题就是性能问题,日志记录功能很可能在release版本也需要保留,多次执行或者在主线程进行文件操作会在一定程度上影响app运行效率;

2、不同功能模块区分日志内容仅能通过tag,不便于扩展;

3、安全问题,比如多线程操作同一文件,可能导致文件内容混乱;

基于这些原因,在工作时间之外自己动手写了一个简洁的日志记录框架TextRecorder,现将其开源并分享出来。

介绍

项目地址:TextRecorder

项目特点:

  1. 简洁;
  2. 扩展性强;
  3. 主要适配Android平台;
  4. 线程安全;

日志记录其实每个项目中基础但小众的功能,所以TextRecorder并不以提供非常强大的功能为目标,但通过它的扩展性,基本可实现大部分的功能需求。另外,虽然目前是Java项目,但其主要目标使用平台还是Android平台,当然你也可以完全用于Java项目中。

扩展:当开始进行这个工具类开发的时候,目标仍然是对日志进行文本保存,但后期发现通过它的扩展性,可实现的功能并不仅仅局限如此,它可以是数据库保存、网络保存或者仅仅只在控制台打印文本内容,甚至它能处理的并不只是日志内容,任何文本都可以,基于该原因,我将该框架命名为TextRecorder,而不是FileRecorder或者是LogRecorder

使用

添加引用

首先在项目中引入框架,项目目前发布在jcenter仓库上的,

repositories {
    // ...
    jcenter()
}

添加项目核心依赖(必须添加):

compile 'com.github.naturs.text.recorder:text-recorder:1.5.1'

项目还提供了几个扩展依赖,主要是实现对日志进行文件保存,你也可以完全不依赖它们而是自定义实现,后续会介绍到。

两个依赖需一起添加,

compile 'com.github.naturs.text.recorder:text-recorder-converter:1.5.1'
compile 'com.github.naturs.text.recorder:text-recorder-processor:1.5.1'

初始化

在正式使用TextRecorder之前,先介绍一下涉及到的几个Java类及概念:

TextLine:它是一个抽象类,代表的是一个文本记录,它可以包含一个字符串、一个Exception或者一个段落等等,注意:一个TextLine并不一定只是一行数据,它可以同时包含上面的内容;

GenericTextLineTextLine的子类,它主要处理文本、异常、JSON、XML等信息;

TextLineConverter:将一个TextLine转换成字符串的工具;

TextLineProcessor:处理TextLineConverter转换后的字符串的工具;

TextRecorder:文本操作入口,所有的操作都通过该类进行;

TAG:这是一个抽象但很重要的概念,在使用TextRecorder时,会要求传入一个tag,如TextRecorder.with(tag),这个tag类似于Android Log框架的tag标签,用来区分不同的日志类型,这里建议 日志按模块或功能划分,使用不同的tag来区分

我们需要对TextRecorder进行初始化以设置一些默认的配置,否则你需要在每次操作时都指定这些配置。

在你第一次使用TextRecorder之前初始化即可,但在Android平台下,一般会选择在Application中初始化。

初始化方式如下:

TextRecorder.init(
    Scheduler,
    TextLineConverter.Factory,
    TextLineProcessor.Factory,
    LogPrinter
);

其中参数含义如下:

  • Scheduler,代表处理文本的线程,默认使用Schedulers.io()
  • TextLineConverter.FactoryTextLineConverter的工厂对象,每次需要的时候会生产一个TextLineConverter对象;
  • TextLineProcessor.FactoryTextLineProcessor的工厂对象,每次需要的时候会生产一个TextLineProcessor对象;
  • LogPrinter,打印日志的接口,可根据运行环境来配置,比如Android下使用android.util.Log来打印日志;

使用方式

// 参数tag代表日志标签,最终会在Converter或Processor中用到
TextRecorder recorder = TextRecorder.with("module");
// 每一个append都是一条记录,可同时记录多条
recorder.append(String)
        .append(Throwable)
        .appendJson(JSON)
        .appendXml(XML)
        .appendBlankLine()
        .appendDivider();
// 同步提交
recorder.commit();
// OR 异步提交
recorder.apply();

TextRecorder.appendXX()方法是指添加一条文本,每次append都添加一条,也就是说可以同时提交多条日志,最终会按提交的顺序保存。

如果使用默认提供的文本处理方式,最终文本保存效果如下图。

default.png

分析

扩展性

首先看一下执行流程:

1、首先通过TextRecorder将文本内容提交,提交内容只要是TextLine的子类即可;

2、提交后会通过TextLineConverter.Factory生成一个TextLineConverter,将TextLine转换成String

3、最后通过TextLineProcessor.Factory生成一个TextLineProcessor,来处理步骤2中生成的String

执行流程很简单,接下来具体分析一下这3个步骤所带来的扩展性。

1、TextLine的扩展性。一开始开发的时候,想法是封装一个类,里面包含所能考虑到的所有的文本内容,类似于目前的GenericTextLine,但是再完整的封装也不可能满足所有人的需求,所以这里改为了现在的抽象类TextLine,用户可以自定义文本内容,任何内容都可以。

2、TextLineConverter的扩展性。既然文本内容可以自定义,那文本最终处理方式也应该可以自定义。我们可以直接对TextLine进行处理,比如直接将一个TextLine保存到文件中,但是显然在保存操作这个过程中,我们需要将TextLine转换成一个我们可以进行保存操作的对象,所以为了将职责区分开来,使用TextLineConverter来专门处理这一转换操作。

3、TextLineProcessor的扩展性。TextLineProcessor是我们处理文本的最后一步,最终处理TextLine转换后的String

可能有同学对步骤2中TextLineConverter的功能有疑惑,为什么不能通过给TextLine添加抽象方法的形式来代替TextLineConverter呢?比如:


public abstract class TextLine {
    // ...

    public abstract String convert();

    // ...
}

public class MyTextLine {
    // ...

    @Override
    public String convert() {

    }

    // ...
}

我们是否可以将TextLineConverter的功能放入TextLine.convert()方法中呢?

答案当然是可以的,这样我们可以省略掉步骤2,直接在TextLineProcessor中处理TextLine.convert()的结果就可以了。

但是,当我们需要更换转换方式时,比如之前是TextLine -> A_B_C,现在想改为TextLine -> A-B-C,我们就需要控制MyTextLine这个类了,甚至需要直接替换掉该类。从实际开发的角度来看,这一成本是比较大的,因为TextLine可能出现在全局大部分位置,改动困难且无法一次性全局更改。

而且,从设计的角度来看,TextLine应该仅仅关心内容,而不应该关心内容的转换方式,尽量做到职责的单一。

这一设计灵感来源于 Retrofit,大家可以去研究它的源码。

项目提供了一个自定义逻辑的转换方式,用于将普通的文本内容转换为markdown形式的文本,添加如下依赖即可:

compile 'com.github.naturs.text.recorder:text-recorder-markdown:1.5.1'

使用方式如下:

TextRecorder recorder = TextRecorder.with("markdown");

MarkdownTextLine textLine = MarkdownTextLine.with().text("I'm a text.");
recorder.append(textLine);

RuntimeException exception = new RuntimeException("mock an exception.");
textLine = MarkdownTextLine.with().throwable("I'm an exception.", exception);
recorder.append(textLine);

textLine = MarkdownTextLine.with().divider();
recorder.append(textLine);

textLine = MarkdownTextLine.with().json("I'm a json.", Sample.JSON);
recorder.append(textLine);

textLine = MarkdownTextLine.with().xml("I'm a xml.", Sample.XML);
recorder.append(textLine);

recorder.apply();

文本内容渲染后的效果如下:

markdown.png

线程安全

文章开头提过,如果多线程同时操作一个文件,很有可能造成文件内容混乱,所以该框架是采用的单线程存储模式,具体控制逻辑在TextLineEmitLoop类中。该逻辑中参考自 Operator 并发原语:串行访问(serialized access)(一),emitter-loop,就不具体分析了,参考原文即可。

结语

最后说一下项目提供的默认的TextLineConverterTextLineProcessor的效果。

TextLineConverter在上图已经展示出来了,最终是 时间+调用方法+内容 的格式。

TextLineProcessor按模块分目录 存储文件,即不同模块的日志文件放入不同文件夹下,模块名通过TextRecorder.with(tag)传入,最后文件会 按天保存,每天的文件会放入单独的文件中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容