Android日志打印框架xLog超详细使用详解

  • Post author:
  • Post category:其他


在开发过程中必不可少的会用到Logcat去调试程序。正式上线后一般也还得保存手机用户的操作日志或异常日志方便开发人及时确定问题。我前几天发现一个好用的日志框架——XLog,它可以打印出好看的日志格式方便开发人员调试,还能自动生成日志文件,方便上架后收集信息,我已经用在项目上了,推荐给大家!当然,我说的不是指微信mars下的xlog日志,是另一款开源框架。大家习惯看英文文档的可以去官网:

https://github.com/elvishew/xLog

这里是考虑到官网的说明比较简单且是英文的,所以才写了这篇博客,推荐给大家的同时,也是方便以后自己看。(这里demo是用Kotlin写的,毕竟是主流,用java的同学应该也看得懂!)


目录


一、简介


二、XLog基本用法


1.添加依赖


2.初始化


3.基本打印


4.打印json和xml


5.打印集合


6.直接打印Bundle和异常对象


7.占位符打印


8.直接打印对象


三、配置


1、logLevel方法,指定日志级别


2、tag方法,指定标签,默认:“X-LOG”


3、enableThreadInfo方法,打印线程信息


3、enableStackTrace方法,堆栈跟踪信息


4.enableBorder 启用边界


5.jsonFormatter,设置json打印的格式处理类


6.xmlFormatter,设置xml打印的格式处理类


7.throwableFormatter,设置异常信息处理,默认是DefaultThrowableFormatter


8.threadFormatter,当前线程信息打印格式处理,需要和enableThreadInfo配合使用默认是DefaultThreadFormatter


9.stackTraceFormatter,堆栈追踪信息打印格式处理,默认是DefaultStackTraceFormatter,需要和enableStackTrace配合使用


10.addObjectFormatter,为对象的特定类添加格式化器默认是AnyClassObjectFormatter,


11.borderFormatter,边框格式处理器,默认DefaultBorderFormatter,需要和enableBorder配合使用


12.addInterceptor,添加日志拦截器


四、日志写入到文件


1.Builder里面的参数指定日志存放路径


2.backupStrategy,指定单个日志文件的大小


3.cleanStrategy,设置日志文件存活时间


4.fileNameGenerator,设置日志文件的名称


5.flattener设置日志文件内容输出格式,默认DefaultFlattener


一、简介

先看看官网的简介:

Lightweight and pretty, powerful and flexible logger for android and java, 
can print the log to Logcat, Console and Files, or anywhere if you like.

大致意思是说这是一个可以在android和java上使用对的轻量级日志框架,可打印到Logcat和文件中!先上一个官网的效果图吧!

图中可以看出,普通LOG打印就不说了,看看JSON和xml以及集合的打印,它能直接打印出好看的格式,以及线程信息和边框,当然这些都是可以配置的,如果你不喜欢边框你也可以配置不打印边框!而且不用担心字符串过长而导致打印不全的问题,过长的话它会自动换行!

好了,废话不多说,接下来说说怎么用吧!

二、XLog基本用法


1.添加依赖

implementation 'com.elvishew:xlog:1.7.2'


2.初始化

XLog.init(LogLevel.ALL);

建议这段初始化的代码写在你自定义的Application中。当然你写在Activity里面也可以用,反正全局只需要初始化一次就好了!


3.基本打印

基本的打印原生打印方式类似。执行如下代码

        XLog.v("hello xlog");
        XLog.d("hello xlog");
        XLog.i("hello xlog");
        XLog.w("hello xlog");
        XLog.e("hello xlog");

控制台打印出来的结果:


4.打印json和xml

支持打印json和xml,通过XLog.json和XLog.xml打印。执行如下代码代码:

        val jsonObject = JSONObject()
        jsonObject.put("name", "张三")
        jsonObject.put("age", "21")
        jsonObject.put("sex", "女")

        val xmlString = "<team>" +
                "<member name='Elvis'/>" +
                "<member name='Leon'/>" +
                "</team>";
        XLog.json(jsonObject.toString(2))
        XLog.xml(xmlString)

打印结果:


5.打印集合

支持直接打印集合对象。执行如下行代码:

     val list = mutableListOf<String>()
        list.add("11111")
        list.add("22222")
        list.add("33333")
        list.add("44444")
        list.add("55555")

        val map = mutableMapOf<String,String>()
        map["name"] = "张三"
        map["age"] = "12"
        map["sex"] = "男"

        XLog.d(list)
        XLog.d(map)

打印结果:


6.直接打印Bundle和异常对象

支持直接打印

Bundle和

Exception对象,将主要信息打印出来。执行如下代码:

        val b = Bundle()
        b.putString("name","李四")
        b.putInt("age",13)
        b.putString("sex","女")
        XLog.i(b)
        try {
            1/0
        }catch (e:Exception){
            XLog.e(e)
        }

打印结果:


7.占位符打印

支持占位符打印,开发者打印的时候就不用用多个“+”拼接字符串了。执行如下代码:

XLog.d("Hello %s, I am %d", "Elvis", 20)

打印结果


8.直接打印对象



XLog还支持直接打印对象,但是他打印的结果是对象调用toString()后得到的字符串!比如执行如下代码:

   val stu = Student("王五",15,"男")
        XLog.i(stu)
        Log.i("TAG",stu.toString())

打印结果:

这里就会发现,直接打印对象和用原生的打印对象.toString()的结果是一样的!实际上看过源码就知道,打印普通对象的时候他都会调用toString()返回的结果打印到控制台。所以这里我们把Student重写它的toString方法再试一次。

Student类代码如下:

class Student(
  var name:String,
  var age:Int,
  var sex:String
) {
    override fun toString(): String {
        val json = JSONObject()
        json.put("name",this.name)
        json.put("age",this.age)
        json.put("sex",this.sex)
        return json.toString()
    }
}

这里重写了toString,返回一个包含所以属性的json格式的字符串,在执行刚才的代码,打印结果如下:

是吧,所以大家这里要直接打印普通对象看属性的话,还得重写对象的toString方法!这里说的普通对象我做个解释,表达的意思就是XLog不能直接格式化对的对象都看作普通对象,json、xml、Bundle和集合之外的对象,大概这么个意思,不要太纠结!好了,继续~

怎么样?!上面几种打印方式是不是很nice,已经可以满足日常的调试需求的,起码比自带的Log打印要好太多!如果这些已经够用了的话,就可以到此为止了!

当然XLog的强大还不止如此,他还支持自定义属性配置和自动生成打印日志文件,接下来说说吧!

三、配置

先看看官网的说明:

    LogConfiguration config = new LogConfiguration.Builder()
    .logLevel(BuildConfig.DEBUG ? LogLevel.ALL             // Specify log level, logs below this level won't be printed, default: LogLevel.ALL 指定日志级别,此级别以下的日志将不会打印,默认值:LogLevel.ALL
    : LogLevel.NONE)
    .tag("MY_TAG")                                         // Specify TAG, default: "X-LOG" 指定标签,默认:“X-LOG”
    .enableThreadInfo()                                    // Enable thread info, disabled by default 启用线程信息,默认禁用
    .enableStackTrace(2)                                   // Enable stack trace info with depth 2, disabled by default 启用深度为2的堆栈跟踪信息,默认禁用
    .enableBorder()                                        // Enable border, disabled by default 启用边框,默认禁用
    .jsonFormatter(new MyJsonFormatter())                  // Default: DefaultJsonFormatter 默认值:DefaultJsonFormatter
    .xmlFormatter(new MyXmlFormatter())                    // Default: DefaultXmlFormatter 默认值:DefaultXmlFormatter
    .throwableFormatter(new MyThrowableFormatter())        // Default: DefaultThrowableFormatter 默认值:DefaultThrowableFormatter
    .threadFormatter(new MyThreadFormatter())              // Default: DefaultThreadFormatter 默认值:DefaultThreadFormatter
    .stackTraceFormatter(new MyStackTraceFormatter())      // Default: DefaultStackTraceFormatter 默认值:DefaultStackTraceFormatter
    .borderFormatter(new MyBoardFormatter())               // Default: DefaultBorderFormatter 默认值:DefaultBorderFormatter
    .addObjectFormatter(AnyClass.class,                    // Add formatter for specific class of object 为对象的特定类添加格式化器
    new AnyClassObjectFormatter())                     // Use Object.toString() by default 默认情况下使用Object.toString()
    .addInterceptor(new BlacklistTagsFilterInterceptor(    // Add blacklist tags filter 添加黑名单标签过滤器
    "blacklist1", "blacklist2", "blacklist3"))
    .addInterceptor(new MyInterceptor())                   // Add other log interceptor 添加其他日志拦截器
    .build();

    Printer androidPrinter = new AndroidPrinter(true);         // Printer that print the log using android.util.Log 使用android.util.Log打印日志的打印机
    Printer consolePrinter = new ConsolePrinter();             // Printer that print the log to console using System.out 使用System.out将日志打印到控制台的打印机
    Printer filePrinter = new FilePrinter                      // Printer that print(save) the log to file 打印(保存)日志到文件的打印机
    .Builder("<path-to-logs-dir>")                         // Specify the directory path of log file(s) 指定日志文件的目录路径
    .fileNameGenerator(new DateFileNameGenerator())        // Default: ChangelessFileNameGenerator("log") 默认值:ChangelessFileNameGenerator(“日志”)
    .backupStrategy(new NeverBackupStrategy())             // Default: FileSizeBackupStrategy(1024 * 1024) 默认:FileSizeBackupStrategy(1024 * 1024)
    .cleanStrategy(new FileLastModifiedCleanStrategy(MAX_TIME))     // Default: NeverCleanStrategy() 默认值:NeverCleanStrategy ()
    .flattener(new MyFlattener())                          // Default: DefaultFlattener 默认值:DefaultFlattener
    .build();

    XLog.init(                                                 // Initialize XLog 初始化XLog
    config,                                                // Specify the log configuration, if not specified, will use new LogConfiguration.Builder().build() 指定日志配置,如果未指定,将使用新的LogConfiguration.Builder().build()
    androidPrinter,                                        // Specify printers, if no printer is specified, AndroidPrinter(for Android)/ConsolePrinter(for java) will be used. 指定打印机,如果没有指定打印机,AndroidPrinter(用于Android)/ConsolePrinter(用于java)将被使用。
    consolePrinter,
    filePrinter);

官网的文档还是比较简介的,简单的一眼就看得懂,稍微复杂的还得自己研究,基础不太好的可能就头大了!为了照顾下英文不太好的同学,这里用有道词典翻译了一下!这些配置很多就给了个默认值,并没描述起什么作用,刚开始使用起来还是有点阻碍的。没关系,我现在来把这些配置一个一个说清楚,保证大家能轻松使用!


1、logLevel方法,指定日志级别



指定日志级别,此级别以下的日志将不会打印,默认值:LogLevel.ALL,这个很好理解,我们来试一下,执行以下代码:

        //初始化,这一段的初始化方法全局只用执行一次就够了,我这里是为了演示看起来方便!
        val config = LogConfiguration.Builder()
                .logLevel(LogLevel.INFO)
                .build()
        XLog.init(config)




        //打印
        XLog.v("hello xlog");
        XLog.d("hello xlog");
        XLog.i("hello xlog");
        XLog.w("hello xlog");
        XLog.e("hello xlog");

打印结果:

我们设置了打印级别为INFO,那就只打印了INFO级别以上的日志,INFO级别以下的日志都未打印,默认值ALL就是打印所有。

2、tag方法,指定标签,默认:“X-LOG”

细心的同学可能已经注意到了,我们打印的时候一直都未指定标签,所有打印开头都是“X-LOG”标签,tag方法就是用来自定义标签的!修改之前的config配置代码:

        //初始化,这一段的初始化方法全局只用执行一次就够了,我这里是为了演示看起来方便!
        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .build()
        XLog.init(config)

打印结果:

可以看到打印结果的标签已经变成了自定义的“HEFA”值了。

除了全局定义tag之外,当然还支持局部定义tag了,可以通过XLog.tag().d()设置单个打印语句tag。执行代码:

        XLog.v("hello xlog");
        XLog.d("hello xlog");
        XLog.i("hello xlog");
        XLog.tag("DM").w("hello xlog");
        XLog.tag("ZY").e("hello xlog");

打印结果:

从结果看到,后面两条打印结果的tag是我们设置的单独tag了。

3、enableThreadInfo方法,打印线程信息

设置enableThreadInfo()可打印出打印代码时所在的线程信息,写个例子,在配置config中加上enableThreadInfo(),然后我们写两条打印,一条在主线程,一条在子线程。执行代码:

        //初始化,这一段的初始化方法全局只用执行一次就够了,我这里是为了演示看起来方便!
        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .enableThreadInfo()
                .build()
        XLog.init(config)



        //打印
        val jsonObject = JSONObject()
        jsonObject.put("name", "张三")
        jsonObject.put("age", "21")
        jsonObject.put("sex", "女")
        XLog.json(jsonObject.toString(2))

        Thread {
            XLog.json(jsonObject.toString(2))
        }.start()

打印结果:

图中结果可以看出,打印出了线程名称。

3、enableStackTrace方法,堆栈跟踪信息

啥意思我就不说,直接上代码打印结果,一看便知。执行代码

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        //初始化,这一段的初始化方法全局只用执行一次就够了,我这里是为了演示看起来方便!
        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .enableStackTrace(3)
                .build()
        XLog.init(config)


        test1.setOnClickListener {
            testLog()
        }
    }

    fun testLog() {
        //打印
        val jsonObject = JSONObject()
        jsonObject.put("name", "张三")
        jsonObject.put("age", "21")
        jsonObject.put("sex", "女")
        XLog.json(jsonObject.toString(2))

    }

打印结果:

如图打印出了代码执行所在位置,并追踪了三层,参数为 多少就追踪几层,这个大家调试的时候应该看过类似的格式,就不多说了!

4.enableBorder 启用边界

就是打印的时候加上边框,改下配置,其他代码和上面的一样,就不再次贴了,贴下配置代码:

val config = LogConfiguration.Builder()
                .tag("HEFA")
                .enableBorder()
                .build()

打印结果:

很明显,打印结果多了边框。

5.jsonFormatter,设置json打印的格式处理类

打印json的格式默是用DefaultJsonFormatter类处理的。这里可以自定义,我们先看下DefaultJsonFormatter是怎么处理的,上源码:

public class DefaultJsonFormatter implements JsonFormatter {

  private static final int JSON_INDENT = 4;

  @Override
  public String format(String json) {
    String formattedString = null;
    if (json == null || json.trim().length() == 0) {
      throw new FormatException("JSON empty.");
    }
    try {
      if (json.startsWith("{")) {
        JSONObject jsonObject = new JSONObject(json);
        formattedString = jsonObject.toString(JSON_INDENT);
      } else if (json.startsWith("[")) {
        JSONArray jsonArray = new JSONArray(json);
        formattedString = jsonArray.toString(JSON_INDENT);
      } else {
        throw new FormatException("JSON should start with { or [, but found " + json);
      }
    } catch (Exception e) {
      throw new FormatException("Parse JSON error. JSON string:" + json, e);
    }
    return formattedString;
  }
}

代码不多,就是判断字符串是jsonObject格式还是JSONArray,然后在返回缩排后的字符串。一般情况我们用默认的也就够用了,当然如果愣是有什么特殊嗜好,也可以自定义!现在我们自定义一个,创建一个MyJsonFormatter并实现JsonFormatter接口,上代码:

class MyJsonFormatter : JsonFormatter {
    private val JSON_INDENT = 4

    override fun format(json: String?): String {
        var formattedString: String? = null
        if (json == null || json.trim { it <= ' ' }.isEmpty()) {
            throw FormatException("JSON empty.")
        }
        formattedString = try {
            if (json.startsWith("{")) {
                val jsonObject = JSONObject(json)
                jsonObject.toString(JSON_INDENT)
            } else if (json.startsWith("[")) {
                val jsonArray = JSONArray(json)
                jsonArray.toString(JSON_INDENT)
            } else {
                throw FormatException("JSON should start with { or [, but found $json")
            }
        } catch (e: Exception) {
            throw FormatException("Parse JSON error. JSON string:$json", e)
        }
        formattedString = "我最帅============$formattedString"
        formattedString +="============我最帅"
        return formattedString!!
    }
}

很简单,就把默认的代码复制一下然后在前后加了一段不要脸的字符串,然后加上配置,看看打印结果

        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .jsonFormatter(MyJsonFormatter())
                .build()
        XLog.init(config)

好啦,这只是最简单的自定义,如果想要复杂的自己发挥想象吧!

6.xmlFormatter,设置xml打印的格式处理类

xmlFormatter方法时设置xml打印格式的,和jsonFormatter类似!默认是,DefaultXmlFormatter,亦可自定义!看下默认的DefaultXmlFormatter源码:

public class DefaultXmlFormatter implements XmlFormatter {

  private static final int XML_INDENT = 4;

  @Override
  public String format(String xml) {
    String formattedString;
    if (xml == null || xml.trim().length() == 0) {
      throw new FormatException("XML empty.");
    }
    try {
      Source xmlInput = new StreamSource(new StringReader(xml));
      StreamResult xmlOutput = new StreamResult(new StringWriter());
      Transformer transformer = TransformerFactory.newInstance().newTransformer();
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
          String.valueOf(XML_INDENT));
      transformer.transform(xmlInput, xmlOutput);
      formattedString = xmlOutput.getWriter().toString().replaceFirst(">", ">"
          + SystemCompat.lineSeparator);
    } catch (Exception e) {
      throw new FormatException("Parse XML error. XML string:" + xml, e);
    }
    return formattedString;
  }
}

要自定义也很简单,和自定义jsonFormatter类似的,从返回的字符串结果上下文章就好了,也就是formattedString,可以加上 前缀或者尾缀或者改变格式也可,发挥自己想象就好了!我这里就不演示了,创建一个类,实现XmlFormatter接口,然后从写里面的方参照自定义jsonFormatter就行!

7.throwableFormatter,设置异常信息处理,默认是DefaultThrowableFormatter

8.threadFormatter,当前线程信息打印格式处理,需要和enableThreadInfo配合使用默认是DefaultThreadFormatter

9.stackTraceFormatter,堆栈追踪信息打印格式处理,默认是DefaultStackTraceFormatter,需要和enableStackTrace配合使用

10.addObjectFormatter,为对象的特定类添加格式化器默认是AnyClassObjectFormatter,

为什么上面四个一起拿出来呢,因为就用默认的就可以满足百分之九十八以上开发人员的调试需求了,实在想自定义就和之前一样的,进入到对应的默认处理器源码里面,看他实现了什么接口,你也创建一个类实现同样的接口,然后按照自己喜欢的要求自定义就好了!这里我就一一写代码出来了,相信大家应该也能依葫芦画瓢的整出来!

11.borderFormatter,边框格式处理器,默认DefaultBorderFormatter,需要和enableBorder配合使用

这个我说一下,默认的边框处理确实有点瑕疵!下图

如图,默认的边框第一行紧接着前缀信息,导致边框左边没有对齐,有强迫症的小兄弟看着肯定会很难受,反正我看着想当别扭!咋整呢?!当然这个也是可以自定义的!我们先看下DefaultBorderFormatter源码!

public class DefaultBorderFormatter implements BorderFormatter {

  private static final char VERTICAL_BORDER_CHAR = '║';

  // Length: 100.
  private static final String TOP_HORIZONTAL_BORDER =
      "╔═════════════════════════════════════════════════" +
          "══════════════════════════════════════════════════";

  // Length: 99.
  private static final String DIVIDER_HORIZONTAL_BORDER =
      "╟─────────────────────────────────────────────────" +
          "──────────────────────────────────────────────────";

  // Length: 100.
  private static final String BOTTOM_HORIZONTAL_BORDER =
      "╚═════════════════════════════════════════════════" +
          "══════════════════════════════════════════════════";

  @Override
  public String format(String[] segments) {
    if (segments == null || segments.length == 0) {
      return "";
    }

    String[] nonNullSegments = new String[segments.length];
    int nonNullCount = 0;
    for (String segment : segments) {
      if (segment != null) {
        nonNullSegments[nonNullCount++] = segment;
      }
    }
    if (nonNullCount == 0) {
      return "";
    }

    StringBuilder msgBuilder = new StringBuilder();
    msgBuilder.append(TOP_HORIZONTAL_BORDER).append(SystemCompat.lineSeparator);
    for (int i = 0; i < nonNullCount; i++) {
      msgBuilder.append(appendVerticalBorder(nonNullSegments[i]));
      if (i != nonNullCount - 1) {
        msgBuilder.append(SystemCompat.lineSeparator).append(DIVIDER_HORIZONTAL_BORDER)
            .append(SystemCompat.lineSeparator);
      } else {
        msgBuilder.append(SystemCompat.lineSeparator).append(BOTTOM_HORIZONTAL_BORDER);
      }
    }
    return msgBuilder.toString();
  }

  /**
   * Add {@value #VERTICAL_BORDER_CHAR} to each line of msg.
   *
   * @param msg the message to add border
   * @return the message with {@value #VERTICAL_BORDER_CHAR} in the start of each line
   */
  private static String appendVerticalBorder(String msg) {
    StringBuilder borderedMsgBuilder = new StringBuilder(msg.length() + 10);
    String[] lines = msg.split(SystemCompat.lineSeparator);
    for (int i = 0, N = lines.length; i < N; i++) {
      if (i != 0) {
        borderedMsgBuilder.append(SystemCompat.lineSeparator);
      }
      String line = lines[i];
      borderedMsgBuilder.append(VERTICAL_BORDER_CHAR).append(line);
    }
    return borderedMsgBuilder.toString();
  }
}

代码不多,即便不多我们也只需要看我们关心的就好了,很容易就可以看出边框和VERTICAL_BORDER_CHAR、TOP_HORIZONTAL_BORDER、DIVIDER_HORIZONTAL_BORDER、BOTTOM_HORIZONTAL_BORDER这四个常量有关,好接下来我们来自定义一个,先创建一个MyBorderFormatter,然后实现BorderFormatter接口,在复制源码的主要代码进行改造!

class MyBorderFormatter : BorderFormatter {
    private val VERTICAL_BORDER_CHAR = '║'

    // Length: 100.
    private val TOP_HORIZONTAL_BORDER = "╔═════════════════════════════════════════════════" +
            "══════════════════════════════════════════════════"

    // Length: 99.
    private val DIVIDER_HORIZONTAL_BORDER = "╟─────────────────────────────────────────────────" +
            "──────────────────────────────────────────────────"

    // Length: 100.
    private val BOTTOM_HORIZONTAL_BORDER = "╚═════════════════════════════════════════════════" +
            "══════════════════════════════════════════════════"


    override fun format(segments: Array<out String>?): String {
        if (segments == null || segments.size == 0) {
            return ""
        }

        val nonNullSegments = arrayOfNulls<String>(segments.size)
        var nonNullCount = 0
        for (segment in segments) {
            if (segment != null) {
                nonNullSegments[nonNullCount++] = segment
            }
        }
        if (nonNullCount == 0) {
            return ""
        }

        val msgBuilder = StringBuilder()
        msgBuilder.append("     ").append(SystemCompat.lineSeparator)
        msgBuilder.append(TOP_HORIZONTAL_BORDER).append(SystemCompat.lineSeparator)
        for (i in 0 until nonNullCount) {
            msgBuilder.append(nonNullSegments[i]?.let { appendVerticalBorder(it) })
            if (i != nonNullCount - 1) {
                msgBuilder.append(SystemCompat.lineSeparator).append(DIVIDER_HORIZONTAL_BORDER)
                        .append(SystemCompat.lineSeparator)
            } else {
                msgBuilder.append(SystemCompat.lineSeparator).append(BOTTOM_HORIZONTAL_BORDER)
            }
        }
        return msgBuilder.toString()
    }

    private fun appendVerticalBorder(msg: String): String? {
        val borderedMsgBuilder = java.lang.StringBuilder(msg.length + 10)
        val lines = msg.split(SystemCompat.lineSeparator.toRegex()).toTypedArray()
        var i = 0
        val N = lines.size
        while (i < N) {
            if (i != 0) {
                borderedMsgBuilder.append(SystemCompat.lineSeparator)
            }
            val line = lines[i]
            borderedMsgBuilder.append(VERTICAL_BORDER_CHAR).append(line)
            i++
        }
        return borderedMsgBuilder.toString()
    }
}

就加了一行代码,在format方法里加上了msgBuilder.append(” “).append(SystemCompat.lineSeparator)将第一行打印信息进行的分行,其他的都是原封不动的复制过来的。我们看看效果

这样是不是舒服多了。你也可以改变边框的样式,比如我把边框的四个变量改一下!

    private val VERTICAL_BORDER_CHAR = '☆'

    // Length: 100.
    private val TOP_HORIZONTAL_BORDER = "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆" +
            "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"

    // Length: 99.
    private val DIVIDER_HORIZONTAL_BORDER = "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆" +
            "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"

    // Length: 100.
    private val BOTTOM_HORIZONTAL_BORDER = "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆" +
            "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"

我把边框线全改成了☆☆,看下效果吧!

是吧!你要想其他的形状的话依葫芦画瓢的改一下就好了,发挥你的想象吧!

12.addInterceptor,添加日志拦截器

这里框架默认提供了一个BlacklistTagsFilterInterceptor的标签过滤器,看下怎用

        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .enableBorder()
                .borderFormatter(MyBorderFormatter())
                .addInterceptor(BlacklistTagsFilterInterceptor("blacklist1", "blacklist2", "blacklist3")) // Add blacklist tags filter 添加黑名单标签过滤器
                .build()
        XLog.init(config)








        XLog.tag("blacklist1").d("11111111111111111111111")
        XLog.tag("blacklist2").d("11111111111111111111111")
        XLog.tag("blacklist3").d("11111111111111111111111")
        XLog.tag("blacklist4").d("11111111111111111111111")
        XLog.tag("blacklist5").d("11111111111111111111111")
        XLog.tag("blacklist6").d("11111111111111111111111")

添加.addInterceptor(BlacklistTagsFilterInterceptor(“blacklist1”, “blacklist2”, “blacklist3”)) 来过滤指定的tag日志看看结果

指定的123标签日志被过滤掉了并没有打印。当然也可以自定义日志拦截器,同之前思路一样,创建一个类,然后继承AbstractFilterInterceptor,你就可以 参考BlacklistTagsFilterInterceptor的处理方式,想怎么玩就怎么玩!我这里也就不演示了,因为确实大部分场景都用不到这个东西!

四、日志写入到文件

日志自动写入到文件也是这个框架的一大特色了!我们先看看官方给的配置信息

var filePrinter = FilePrinter                      // Printer that print(save) the log to file 打印(保存)日志到文件的打印机
                .Builder(getExternalFilesDir(null)?.path)// Specify the directory path of log file(s) 指定日志文件的目录路径
                .fileNameGenerator(DateFileNameGenerator()) //自定义文件名称 默认值:ChangelessFileNameGenerator(“日志”)
                .backupStrategy(FileSizeBackupStrategy(3 * 1024 * 1024)) //单个日志文件的大小默认:FileSizeBackupStrategy(1024 * 1024)
                .cleanStrategy( FileLastModifiedCleanStrategy (  30L * 24L * 60L * 60L * 1000L))  //日志文件存活时间,单位毫秒
                .flattener(DefaultFlattener()) //自定义flattener,控制打印格式
                .build();

这样的配置其实已经可以用了,我们在初始化一下

XLog.init(config,filePrinter)

执行日志之后我么就可以在设备文件夹看到生成的日志了,默认是以日期为文件名称的

我们再看看文件内容

是不是很nice,简单几行配置代码就可以将日志写入到文件,到时候只需要写上传文件的方法,就可以将日志文件上传到后台,方便开发者定位线上的问题!

不过这种配置只能将日志输出的文件,没法在控制台上打印,还需要加上一行配置

var androidPrinter =  AndroidPrinter(true);  


XLog.init(config,androidPrinter,filePrinter)

再看看结果:

这样日志就同时输出到文件中和控制台,我们再来看看配置方法

1.Builder里面的参数指定日志存放路径

这个没什么好说的,就只传路径参数就好了

2.backupStrategy,指定单个日志文件的大小

也没什么好说的,很好理解,设置单个日志文件的最大存储量,超过指定大小则会另外创建一个文件

3.cleanStrategy,设置日志文件存活时间

单位毫秒。当日志文件超过指定的时间,在下次执行打印的时候就会被清除!防止日志文件过多占用存储空间!这里要注意int类型数据溢出哦!建议用long类型的参数!

4.fileNameGenerator,设置日志文件的名称

通过之前的测试,我们可以看到,生成的日志文件默认是当前日期作为文件名!如果不喜欢可以自定义!我们先看看默认规则DateFileNameGenerator的源码

public class DateFileNameGenerator implements FileNameGenerator {

  ThreadLocal<SimpleDateFormat> mLocalDateFormat = new ThreadLocal<SimpleDateFormat>() {

    @Override
    protected SimpleDateFormat initialValue() {
      return new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    }
  };

  @Override
  public boolean isFileNameChangeable() {
    return true;
  }

  /**
   * Generate a file name which represent a specific date.
   */
  @Override
  public String generateFileName(int logLevel, long timestamp) {
    SimpleDateFormat sdf = mLocalDateFormat.get();
    sdf.setTimeZone(TimeZone.getDefault());
    return sdf.format(new Date(timestamp));
  }
}

代码很简单,就是获取了当前“yyyy-MM-dd”格式的日期作为名称,现在我们来自定义一个规则,创建一个MyFileNameGenerator,实现FileNameGenerator接口

class MyFileNameGenerator : FileNameGenerator {
    override fun isFileNameChangeable(): Boolean {
        return true
    }

    override fun generateFileName(logLevel: Int, timestamp: Long): String {
        @SuppressLint("SimpleDateFormat")
        val sdf = SimpleDateFormat("yyyy-MM-dd")
        return LogLevel.getLevelName(logLevel) + "-" + "myLog" + "-" + sdf.format(Date(timestamp)) + ".log"

    }
}

规则很简单,日志级别-“mylog”-当前日期.log,然后更改fileNameGenerator配置为MyFileNameGenerator()

 var filePrinter = FilePrinter                      // Printer that print(save) the log to file 打印(保存)日志到文件的打印机
                .Builder(getExternalFilesDir(null)?.path)// Specify the directory path of log file(s) 指定日志文件的目录路径
                .fileNameGenerator(MyFileNameGenerator()) //自定义文件名称 默认值:ChangelessFileNameGenerator(“日志”)
                .backupStrategy(FileSizeBackupStrategy(3 * 1024 * 1024)) //单个日志文件的大小默认:FileSizeBackupStrategy(1024 * 1024)
                .cleanStrategy( FileLastModifiedCleanStrategy (  30L * 24L * 60L * 60L * 1000L))  //日志文件存活时间,单位毫秒
                .flattener(DefaultFlattener()) //自定义flattener,控制打印格式
                .build();




//打印代码
        val jsonObject = JSONObject()
        jsonObject.put("name", "张三")
        jsonObject.put("age", "21")
        jsonObject.put("sex", "女")
        XLog.json(jsonObject.toString(2))
        XLog.e("123465789")
        XLog.i("adgadafdasdf")

我们看看效果

如图看出,根据日志级别分别生成了对应的三个文件,怎么样?接下来你们依葫芦画瓢想想定义什么名就定义什么名!

5.flattener设置日志文件内容输出格式,默认DefaultFlattener

我们先看看默认的格式,默认的格式是时间戳|日志级别|tag

当然这个也可以自定义,我们先看看DefaultFlattener源码:

public class DefaultFlattener implements Flattener, Flattener2 {

  @Override
  public CharSequence flatten(int logLevel, String tag, String message) {
    return flatten(System.currentTimeMillis(), logLevel, tag, message);
  }

  @Override
  public CharSequence flatten(long timeMillis, int logLevel, String tag, String message) {
    return Long.toString(timeMillis)
        + '|' + LogLevel.getShortLevelName(logLevel)
        + '|' + tag
        + '|' + message;
  }
}

代码很简单,我就不说了,我们来自定义一个,创建MyFlattener类并实现Flattener, Flattener2两个接口

class MyFlattener  : Flattener, Flattener2 {
    override fun flatten(logLevel: Int, tag: String?, message: String?): CharSequence {
        return flatten(System.currentTimeMillis(), logLevel, tag, message)
    }

    override fun flatten(timeMillis: Long, logLevel: Int, tag: String?, message: String?): CharSequence {
        return (getCurrDDate()
                + '|' + LogLevel.getLevelName(logLevel)
                + '|' + tag
                + '|' + message)
    }

    fun getCurrDDate(): String? {
        return if (Build.VERSION.SDK_INT >= 24) {
            SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(Date())
        } else {
            val tms = Calendar.getInstance()
            tms[Calendar.YEAR].toString() + "-" + tms[Calendar.MONTH] + "-" + tms[Calendar.DAY_OF_MONTH] + " " + tms[Calendar.HOUR_OF_DAY] + ":" + tms[Calendar.MINUTE] + ":" + tms[Calendar.SECOND] + "." + tms[Calendar.MILLISECOND]
        }
    }
}

我们只是参照默认的将时间戳改成时间字符串!然后在配置那里将flattener的参数设置成MyFlattener()看看效果

这下舒服多了吧,也方便排查问题,好了剩下的你们想怎么定义就怎么定义吧!

最后放一下完整的写入文件配置信息,包含自定义的配置

        val config = LogConfiguration.Builder()
                .tag("HEFA")
                .enableBorder()
                .borderFormatter(MyBorderFormatter())
                .build()
        var androidPrinter =  AndroidPrinter(true);         // Printer that print the log using android.util.Log 使用android.util.Log打印日志的打印机
        var filePrinter = FilePrinter                      // Printer that print(save) the log to file 打印(保存)日志到文件的打印机
                .Builder(getExternalFilesDir(null)?.path)// Specify the directory path of log file(s) 指定日志文件的目录路径
                .fileNameGenerator(MyFileNameGenerator()) //自定义文件名称 默认值:ChangelessFileNameGenerator(“日志”)
                .backupStrategy(FileSizeBackupStrategy(3 * 1024 * 1024)) //单个日志文件的大小默认:FileSizeBackupStrategy(1024 * 1024)
                .cleanStrategy( FileLastModifiedCleanStrategy (  30L * 24L * 60L * 60L * 1000L))  //日志文件存活时间,单位毫秒
                .flattener(MyFlattener()) //自定义flattener,控制打印格式
                .build();

        XLog.init(config,androidPrinter, filePrinter)

好了,完结撒花~



版权声明:本文为qq_36611526原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。