前言

现在软件开发门槛越来越低,随便一个大学生培训几个月就可以开始开发软件,但是事实上,软件开发是有较高门槛的,写出高质量的代码并不容易。按28定律,只有20%程序员能写出合格的代码,其他80%代码质量都是有问题的。按从业多年接触的经验上看,竞然高达有80%的人不注重日志实践,甚至连日志实践都很肤浅。不夸张地说,面试程序员时,如果连接日志实践都无法说清楚的,几乎可以肯定是没有经验的或水平很一般的。
有经验的人在构建应用程序基础框架时,一般会前置构建好日志模块。笔者从事的物联网设备开发领域有很多使用Android开发的设备,水平不一的Android程序员经常开发出崩溃、闪退、ANR等问题设备的,在Android开始时,日志实践更是重要,一般我们应该结合自身的应用特点定义和配置好日志模块。

常见的Android日志框架包括:

  • 原生logcat
    Android原生内置的日志模块,与Android Studio结合紧密,相当于Chrome中的console,算是比较基础的日志模块。
    零配置,直接log.d,log.ilog.w,log.e就可以在控制台输出日志。 一般,我们就会release版本中关闭日志输出功能以减少开销。
  • Logger

这是一个应用比较广泛的Android日志库,一款简单优秀强大的Android日志记录器Logger,有相当多的人在使用。优点是能提供线程/类/方法信息,支持JSON/XML格式化打印,输出格式简洁易看、能直接跳转到源文件。
但是Logger只是专门针对Android开发的,对一个完整的日志框架而言,并不完善。官方只包含一个DiskLogAdapter,如果要将日志输出到SocketHTTPsyslog等,需要自己开发Adapter

  • android-logback
    在Java领域,SLF4J是一个著名的JAVA日志门面框架,[logback](http://www.logback.cn/)是其实现。logback 继承自 log4j,它建立在有十年工业经验的日志系统之上。它比其它所有的日志系统更快并且更小,包含了许多独特并且有用的特性。android-logback则是其Android封装。
  • 其他Android日志框架

还有不少第三方开源的日志框架,如ViseLog,log4j,Hutool/logUntil等等。

日志需求

日志模块实践和配置应与需求相关联,笔者需要开发一套基于Android的物联网设备,不同于一般的Android App,其对日志的需求如下:

  • 支持输出到logcat,并且能自己输出格式,最重要的是要能跳转到源码文件。
  • 支持输出到文件,能按日期、日志文件大小、数量切割滚动输出日志。
  • 能输出到Sentry日志收集平台
  • 能输出到Syslog服务器
  • 能灵活配置日志参数,并可以在运行中修改日志级别、启用/禁用网络日志等。

基于上述需求,最终选型使用android-logback.

基本使用方法

安装

build.gradle中添加

    implementation 'org.slf4j:slf4j-api:1.7.25'
    implementation 'com.github.tony19:logback-android:2.0.0'

logback-android提供了多种Appender,用来实现将日志输出到不同的目标,如下:

XML配置

app/assets下创建一个logback.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <!-- 日志保存位置   -->
    <property name="LOG_DIR" value="/data/data/包名/files/logs" />

    <!-- Logcat -->
    <appender name="LOGCAT" class="ch.qos.logback.classic.android.LogcatAppender">
        <tagEncoder>
            <pattern>%logger{12}</pattern>
        </tagEncoder>
        <encoder>
            <pattern>[%-5level] - %msg\, %caller%\,L%line\,%thread%, %line%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--  Syslog   -->
    <appender name="syslog" class="ch.qos.logback.classic.net.SyslogAppender">
        <lazy>true</lazy>
        <syslogHost>192.168.118.129</syslogHost>
        <port>514</port>
        <facility>LOCAL1</facility>
        <reconnectionDelay>10000</reconnectionDelay>
        <suffixPattern>[%thread] %logger %msg</suffixPattern>
        <charset>UTF-8</charset>
    </appender>
    <appender name="SYSLOG" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="syslog" />
    </appender>
    <!-- 滚动文件   -->
    <appender name="rollingfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %class{16} - %msg, L%line, %thread%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
            <maxFileSize>20MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>100MB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <appender name="FILE" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="rollingfile" />
    </appender>
    <root level="DEBUG">
        <appender-ref ref="LOGCAT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="SYSLOG" />
    </root>

</configuration>

关于android-logback的很多配置参数均可以参考logback文档。

需要注意的是针对文件、网络等输出目标,一般应配置AsyncAppenderAsyncAppender的作用简单也就仅仅是内置一个队列,然后批量进行日志输出。logcarAppender不需要。

使用

接下来在类中使用即可。


public class MyClass {
    private final Logger log = LoggerFactory.getLogger(MyClass.class);
    ...
    public void method(){
      
      log.debug(...);
      log.info(...);
      log.warn(...);
      log.error(...);
    
    }
}

我们需要使用LoggerFactory.getLogger(类名)构建一个日志记录器,一般会使用当前类的类名作为logger的名称。

事实上,你完全也可以使用LoggerFactory.getLogger(任意字符串)构建,但使用类名可以得用类名形成层级关系,可以进行更加灵活的日志输出和控制。

至此,基本使用就算完成了。

但是,为了使用log,我们不得不写上大量的样板代码private final Logger log = LoggerFactory.getLogger(MyClass.class);,明显是很不爽的。

一般推荐使用lombok注解来简化此操作。如上例:


@Slf4j
public class MyClass {
    ....
    public void method(){
      
      log.debug(...);
      log.info(...);
      log.warn(...);
      log.error(...);
    
    }
}

只需要在所有类前添加一个@Slf4j的注解即可达到同样的效果,很时显清爽多了。

进阶开发和配置

基本配置并不能满足我们的需求,我们还需要解决:

  • 在logcat中输出
  • 输出到Syslog
  • 输出到Sentry

输出到logcat

android-logback已经实现了logcatAppender,我们只需要配置相应的参数即可。

    <appender name="LOGCAT" class="ch.qos.logback.classic.android.LogcatAppender">
        <tagEncoder>
            <pattern>%logger{12}</pattern>
        </tagEncoder>
        <encoder>
            <pattern>[%-5level] - %msg\, %caller%\,L%line\,%thread%, %line%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

只需要在按照logback[PatternLayout](http://www.logback.cn/06%E7%AC%AC%E5%85%AD%E7%AB%A0Layouts.html)规则配置pattern即可输出丰富的日志信息。

接下来我们还要解决一个问题,在logcat日志输出窗口直接跳转到输出日志所在的源文件。

方法很简单,只需要在pattern中添加.(%file:%line\)即可。

    <appender name="LOGCAT" class="ch.qos.logback.classic.android.LogcatAppender">
              ... 
        <encoder>
            <pattern>[%-5level]", "%msg, .(%file:%line\\) , %thread </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

这样,每一条输出到logcat控制台的日志均会包含一个超链接,点击就可以直接跳转到打印日志时的源文件所在行。

输出至Syslog

syslog是Linux内置的标准日志功能,一般linux均至少会内置syslog clientsyslog server需要额外开启。

只所以在Android开发中启用syslog的原因是,公司的物联网设备即有Android的,也有嵌入式linux的,而在网络收集日志时,嵌入式Linux设备内置了syslog输出,我们希望使用统一的、现成的日志服务器。因此就有了这样的网络日志收集方案:

  • 使用一台linux服务器,配置启用syslog服务器。或者windows上安装Kiwi Syslog Server
  • 嵌入式Linux设备直接使用内置现成的syslog进行网络日志输出,即可以输出应用日志,还可以输出系统日志。
  • Android使用android-logback,但是配置启用syslogAppender.

以上就是我们需要启用syslog的原因。

 <appender name="syslog" class="ch.qos.logback.classic.net.SyslogAppender">
        <lazy>true</lazy>
        <syslogHost>192.168.118.129</syslogHost>
        <port>514</port>
        <facility>LOCAL1</facility>
        <reconnectionDelay>10000</reconnectionDelay>
        <suffixPattern>[%thread] %logger %msg</suffixPattern>
        <charset>UTF-8</charset>
    </appender>
    <appender name="SYSLOG" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="syslog" />
    </appender>

SyslogAppender的配置方式如上,suffixPattern就是用来指定输出日志的信息格式的。配置格式参照[PatternLayout](http://www.logback.cn/06%E7%AC%AC%E5%85%AD%E7%AB%A0Layouts.html)即可。

输出至sentry

sentry是用python/django写的一个通用开源的日志收集服务器,可以说功能相当强大,最关键的是各种语言的日志输出客户端都有,一般可以部署来进行日志收集。

使用sentry相当复杂,部署也比较重,一般使用官方的docker-compose进行部署,部署完毕后容器高达20几个,可以是一个相当重量级的应用,优点是可以进行大规模集群部署。
可以说,小型应用不推荐。

sentry官方就提供了sentry-logback库,直接引入就

   implementation('io.sentry:sentry-logback:4.0.0-alpha.2')  {
       exclude group: 'ch.qos.logback'
 }

sentry-logbackandroid-logback有引用冲突,需要配置一下exclude group。

小结

至此,使用android-logback的日志实践已基本可以工作。
但是还有几个问题需要解决:

  1. 静态的logback.xml配置文件不够灵活,我们需要能让用户或应用可以进行少量的配置,如是否启用网络日志等。
  2. 大型的Android应用可能涉及多个Library,如何实现日志的集中分发。

未完待续


zhangfisher
12 声望2 粉丝