67、日志系统:Log类使用、Logger库、自定义日志输出、日志文件保存

日志这东西,平时开发时觉得不起眼,一旦线上出了bug,它就是你的救命稻草。我见过太多项目,日志打得乱七八糟,出了问题根本没法定位。今天咱们就把日志系统彻底捋一遍。

Android原生Log类,你真的会用吗?

Android自带的android.util.Log,大家肯定都用过。但说实话,很多人只用了皮毛。

Log.v(TAG, "这是verbose日志");
Log.d(TAG, "这是debug日志");
Log.i(TAG, "这是info日志");
Log.w(TAG, "这是warning日志");
Log.e(TAG, "这是error日志");

五个级别,从低到高:VERBOSE、DEBUG、INFO、WARN、ERROR。我个人的习惯是:

  • 开发调试用DEBUG,上线前全部关掉
  • 关键业务流程用INFO
  • 异常捕获用WARN
  • 崩溃和严重错误用ERROR
  • VERBOSE?说实话,我几乎不用,太啰嗦了

小技巧:TAG命名最好统一规范。我一般用类名做TAG,比如LoginActivity。这样看logcat时一眼就知道是哪个类输出的。

为什么需要Logger库?

原生Log有个硬伤——没法控制开关。你想想看,DEBUG日志上线后还在打印,不仅浪费性能,还可能泄露敏感信息。

所以业界普遍用Logger库来封装一层。我早期项目用过Logger、Timber,后来干脆自己写了一个。核心思路就一个:可配置、可开关、可扩展

public class Logger {
    private static boolean isDebug = BuildConfig.DEBUG;
    private static String defaultTag = "AppLog";

    public static void init(boolean debug) {
        isDebug = debug;
    }

    public static void d(String tag, String msg) {
        if (isDebug) {
            Log.d(tag, msg);
        }
    }

    public static void e(String tag, String msg) {
        // 错误日志即使上线也保留
        Log.e(tag, msg);
        // 同时写入文件
        writeToFile(tag, "ERROR", msg);
    }
}

你看,这样一封装,DEBUG日志只在debug模式下打印,而ERROR日志无论什么模式都保留,并且自动写入文件。这就是Logger库的价值所在。

核心原则:日志系统要区分「开发用」和「运维用」。开发日志可以随意,运维日志必须持久化。

自定义日志输出,满足你的特殊需求

有时候,光打印到logcat不够。比如你需要:

  • 把日志发送到远程服务器
  • 把日志写入本地文件
  • 把日志格式化后展示在调试界面

这时候就需要自定义输出策略。我常用的做法是定义一个接口:

public interface LogPrinter {
    void print(int level, String tag, String message);
}

然后实现不同的Printer:

public class FilePrinter implements LogPrinter {
    private File logFile;

    public FilePrinter(File file) {
        this.logFile = file;
    }

    @Override
    public void print(int level, String tag, String message) {
        String line = formatLog(level, tag, message);
        writeLineToFile(line);
    }
}

这样做的好处是啥?解耦。你想加什么输出方式,就加一个Printer实现类,完全不影响现有逻辑。

注意:文件写入操作一定要放在子线程。我曾经见过有人直接在UI线程写日志文件,结果卡到ANR。日志系统本身不能成为性能瓶颈。

日志文件保存,这些坑你踩过吗?

日志写到文件里,听起来简单,但细节很多。我总结了几条经验:

问题 解决方案
日志文件越来越大 按天分割,设置最大大小(比如5MB)
磁盘空间被占满 自动清理7天前的日志
多进程同时写 加文件锁,或者用独立进程写日志
日志内容敏感 脱敏处理,比如手机号中间四位用****代替

嗯,这里要特别说一下文件分割。我习惯用日期+序号命名:

log_2024_01_15_001.txt
log_2024_01_15_002.txt

每个文件达到上限就新建一个。这样查找某一天的日志非常方便。

我的做法:日志文件保存在应用的私有目录下,不要放在SD卡公共目录。一方面安全,另一方面Android 11以后对公共目录访问限制更严了。

知识体系总览

下面这张图,把日志系统的核心脉络画出来了。你看一眼就能明白整体架构:

Android日志系统架构 应用代码 第三方SDK 系统组件 Logger封装层(可配置开关) Logcat输出 文件输出 远程输出 文件管理(分割、清理、脱敏)

从图上能看出来,日志系统分四层:来源层、封装层、输出层、管理层。每一层各司其职,互不干扰。这就是我推崇的架构思路。

避坑指南

最后分享几个我踩过的坑:

  • 不要打印敏感信息:我曾经在日志里打了用户密码,结果被安全团队通报批评。密码、token、身份证号,一律脱敏或直接不打。
  • 控制日志频率:循环里打日志要小心。有一次我在for循环里打了DEBUG日志,结果一次操作生成了几百MB的日志文件。
  • 异步写入要防丢失:应用崩溃时,内存中的日志可能来不及写入文件。我的做法是每写一条就flush一次,虽然性能差点,但数据安全更重要。

日志系统看似简单,但做好不容易。它就像飞机的黑匣子,平时没人关注,出事时才知道有多重要。希望今天的分享能帮你少走弯路。


公众号:蓝海资料掘金营,微信deep3321