美文网首页Android技术知识
Android 开源:日志记录工具 TextRecorder

Android 开源:日志记录工具 TextRecorder

作者: naturs | 来源:发表于2017-07-16 00:00 被阅读187次

    前言

    之前在项目中将一些日志内容保存到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)传入,最后文件会 按天保存,每天的文件会放入单独的文件中。

    相关文章

      网友评论

        本文标题:Android 开源:日志记录工具 TextRecorder

        本文链接:https://www.haomeiwen.com/subject/loazhxtx.html