直接上手干活。做Android开发,没人能绕开Logcat,但很多人对它的理解停留在“IDE底部有个日志面板,能打印东西”这个层面。真正去追线上问题、分析系统级崩溃、从几百兆日志里捞关键线索的时候,Logcat的用法差别会直接决定你Debug的效率。这篇文章从实际项目出发,把Logcat从代码写入到查看技巧完整捋一遍,该给的代码给代码,该讲的坑讲清楚,适合刚入门的新人,也适合偶尔被日志折磨得头秃的熟手。
1. 内容整体设计与思路拆解
1.1 日志不只是“看报错”,它是App运行过程的录音带
我最早接触Logcat的时候,觉得它就是个“错误输出框”。后来慢慢踩坑才理解,日志的本质不是告诉你“哪里错了”,而是还原“当时的现场”。Logcat会按照时间顺序记录系统进程和你App进程里发生的事件——包括Activity生命周期切换、GC回收、线程调度、网络请求异常、崩溃堆栈、电量变化等等。它干的事情,相当于飞机上的黑匣子。
你拿到一台没插调试线的手机,App闪退了。你没法直接看内存,没法打断点,这时候唯一能还原现场的工具就是Logcat——前提是之前已经保存了日志。所以我在团队内部经常强调:日志不是为了“开发时爽”,是为了“出事时能查”。把Logcat从“控制台”思维切换到“证据链”思维,你用它的方式会完全不一样。
具体到技术层面,Logcat其实不是一个IDE功能,而是Android系统内一个独立的日志系统组件。Logcat是Android系统运行时的日志输出与查看工具,它是系统架构的一部分,和你的App、系统服务、内核进程都有关联。Android Studio只是把这个能力接到了IDE界面里,让你在开发时方便查看;而通过adb命令,你也可以在终端里直接抓取任意设备的日志。这套机制统称为“日志系统”,它由日志缓冲区(Log Buffer)、日志读写接口(Logger)、日志守护进程(logd)和系统读取工具共同组成。
1.2 从“能打印”到“会用”:3个能力层级
我见过一些简历上写“熟悉Logcat”的候选人,实际上只会Log.d和Log.e。按我的标准,Logcat的使用能力可以分三个层级:
- 第一层级——能打日志:知道Log.v/d/i/w/e的区别,知道带Tag,能区分不同级别。
- 第二层级——能查日志:会用Android Studio面板的包名过滤、级别过滤、关键字搜索;会用adb logcat命令行抓取日志到文件;会按缓冲区类型查看系统和崩溃日志;能处理日志刷屏和丢日志的问题。
- 第三层级——能用日志解决问题:能在日志里精准定位一次性能卡顿、一次网络超时、一次组件间信息传递失败;能结合多个缓冲区的日志还原完整现场;能通过持久化日志和抓取脚本搭建自己的日志采集体系。
这篇文章的实操部分,主要覆盖第二层级的完整内容,同时会在第三层级上多讲一些思路。你掌握了这些,才算真正会“用”Logcat,而不是“看”Logcat。
1.3 为什么很多项目组“天天打日志,出事时却啥也查不到”
这问题在团队里出现过不止一次。最常见的情况是:线上崩溃,研发打开logcat想看崩溃堆栈,结果发现日志面板里干干净净,或者只有一堆无意义的刷屏日志;另一种情况是,开发时印象里打过Log.e的代码,线上出问题时根本找不到对应日志,拍脑袋也还原不了现场。
这里面有四个典型的坑:
- 日志级别用错:把普通信息都塞进Log.e里,导致真正重要的错误被淹没。
- Tag起得随意:几十个类共用同一个Tag,检索时完全无法缩小范围。
- 日志没有持久化:Logcat缓冲区有大小限制,App退出、缓冲区被冲掉后,日志就没了。
- 混淆后堆栈看不懂:Release包混淆后日志里的类名、方法名全部变成a.b.c,等于没有日志。
这些问题的解法,事实上不复杂,关键在于把“写日志”当成代码设计的一部分来规范,而不是随手print。后面我会逐一展开。
2. 核心细节解析与实操要点
2.1 五个日志级别到底什么场景用,别再一锅粥了
Android的Log类提供了五个静态方法:Log.v、Log.d、Log.i、Log.w、Log.e,分别对应Verbose、Debug、Info、Warn、Error五个级别。很多人纠结“哪个方法对应哪个颜色”、“哪个优先级高”,但其实只要记住一条原则就够了:这个日志在生产环境中被看到时,应该处于什么位置。
- Log.v,Verbose(冗余):最啰嗦的日志,一般只用于开发期的临时调试,比如打印某个方法每帧调用的参数。发布后务必删除或注释,因为对性能影响大,且毫无保留价值。
- Log.d,Debug(调试):用于开发阶段跟踪流程走向,比如Activity的onCreate里打印“参数解析完成”。Release包中建议用BuildConfig.DEBUG包一层,只在Debug包输出。
- Log.i,Info(信息):记录关键节点信息,比如App启动完成、用户登录成功、网络请求发起,这类日志在Release包中通常会保留。
- Log.w,Warn(警告):不致命但值得警惕的情况,比如接口返回了异常的code、缓存被清空但数据还在、使用了过时API。这类日志最容易被忽略,但排查线上问题往往靠它们才能定位到“出问题前发生了什么”。
- Log.e,Error(错误):真正的异常,比如try-catch捕获的Exception、网络请求失败、数据库写入失败。这里要特别提醒:不是只有Exception才打Log.e,未捕获的异常信息进入系统时也会自动归到Error级别,所以不要过度滥用。
提示:别把Log.e当Log.d用。日志级别一旦混乱,过滤功能就废了。我们团队后来甚至强制要求:Log.d必须包在BuildConfig.DEBUG里,线上包只保留Log.i及以上级别,效果立竿见影。
2.2 Tag命名的4条硬规则
Tag是Logcat里最核心的检索维度,但大多数人起Tag是顺手写的,比如类名缩写,或者干脆就是Log.d("TAG", "xxx")。这会导致一个现实问题:当你需要从一个几百MB的日志文件里捞一条关键日志时,Tag给不了任何帮助。
我现在的习惯是:
- 固定前缀:比如团队缩写加模块名,例如“ZS_OrderManager”,一眼就能看出来哪个业务模块。
- 必须有语义:尽量用类名或者组件名,不要用“TEST”“DEBUG”“111”这种无意义字符串。
- 一个类一个Tag:不要一个Activity里出现三四个Tag,后期找日志只会更乱。
- 常量字符串而非方法调用:Tag建议声明为private static final String TAG = "xxx",不要每个方法里写Log.d(getClass().getSimpleName() + "abc", ...),这既影响性能又增加日志量。
性能这里多说一句:getClass().getSimpleName()反射调用有开销,在频繁调用的方法里配合Log输出,很影响帧率和功耗。Tag写成常量,既快又稳。
2.3 字符串拼接是隐形性能杀手
写日志最容易被忽视的问题是字符串拼接。很多人写:
Log.e(TAG, "user login failed, userId = " + userId + ", code = " + code + ", message = " + msg);这段代码的问题在于:无论日志是否输出,字符串拼接都会执行。而字符串拼接是创建新对象的过程,在循环里高频执行,会造成大量短命对象,进而触发GC,最终导致界面卡顿。
正确姿势是用占位符:
Log.e(TAG, String.format("user login failed, userId = %s, code = %d, message = %s", userId, code, msg));如果你用的是Kotlin,字符串模板看起来简单,但底层同样会有StringBuilder操作,频繁调用时同样有开销。所以我的经验是:
- 高频代码路径(onDraw、getView、循环体)里,要么不打日志,要么打最简短的Log。
- 低频事件(点击、网络请求、页面切换)随便打,用String.format规范格式。
- 打日志前可以先判断级别是否开启:
if (Log.isLoggable(TAG, Log.DEBUG)) { ... },这样Release包连日志计算都省了。
2.4 为什么Logger缓冲区有限,Log.d会“消失了”
很多人遇到过这种情况:正在调试一个bug,界面一崩溃,回到Android Studio一看,日志区空了,刚才的调试日志全没了。原因在于Logcat的内核缓冲区是环形结构,默认情况下大小有限,新日志写入会不断覆盖旧日志。
Android设备的日志缓冲区按类型划分,主要有main(应用主日志)、system(系统日志)、crash(崩溃日志)、events(事件日志,比如Activity生命周期、Input事件)、radio(通信日志)这几个。不同设备类型、不同Android版本的默认buffer大小不一样,但整体上都不算大。
这就带来一个真实的痛点:当App持续输出大量日志时,你最关心的那条可能早就被冲掉了。解决办法有两个方向:
- 减少无效日志。日志应该按需打,不该把Logcat当成垃圾场。这也是为什么我在第一节强调规范日志级别和Tag,不是道德洁癖,是缓冲区真的有限。
- 使用持久化方案。开发时用adb logcat保存到文件;线上运行用日志库把日志写入文件并定期上传。这样即使缓冲区被冲掉,原始日志还在你的设备磁盘里。
后面第3节我会给出完整的保存方案,这里只是先让大家建立概念:Logcat是环形缓冲区,不是无限存储,它的数据是“流动”的。
3. 实操过程与核心环节实现
3.1 轻松上手:Android Studio里查Logcat的正确打开方式
Android Studio的Logcat面板在View -> Tool Windows -> Logcat(或者直接点左下角的Logcat按钮)。连接设备、运行App后,面板会实时滚动输出日志。界面看起来唬人,其实核心功能就几个:
- 过滤下拉框:可以选“Show only selected application”,只看当前选中进程的日志;也可以选“No Filters”,看整个设备所有进程的日志,后者在排查系统问题时很有用。
- Log Level下拉框:从Verbose到Error选一个最低级别,低于这个级别的日志全部隐藏。实际工作中我一般用Debug或者Info,Error级别过滤太狠,会把前因后果都滤掉。
- 搜索框:支持关键字搜索,更专业的是支持正则表达式。比如搜“(Exception|Error)”可以批量匹配异常关键词。
- 时间线:面板顶部显示每条日志的精确时间,可以根据时间定位某次具体操作,也可以按时间范围圈选日志再导出。
- 底部查找框:在已经加载的日志里进行当前页搜索,和搜索框(实时过滤)逻辑不同。
实际操作中经常有人问:为什么我点了某个按钮,日志突然不见了?这通常是因为左上角的下拉框选错了设备或进程,或者Log Level调得太高。我之前遇到过一个很搞笑的状况:同事调了两小时日志,说“代码没执行”,我看了一眼他的Logcat面板,右上角过滤的是一个早已断开的旧模拟器设备。所以第一件事永远是确认:当前窗口对应的是哪个进程、哪个设备。
3.2 10倍效率:用好Logcat的过滤器配置
日志面板如果只是全局滚动,那信息密度极大,根本看不过来。Android Studio提供了一个让你保存过滤条件的特性:点击Logcat面板左侧漏斗图标,可以添加自定义过滤器,保存后类似书签,下次一键切换。
我自己的过滤配置模板是这样的:
- 当前页面层:包名过滤 + 级别Info以上,用于日常开发看流程。
- 异常定位:只搜“AndroidRuntime|FATAL EXCEPTION|Process:”,用于快速找崩溃点。
- 网络层:自定义Tag匹配“OkHttp|Retrofit”,用于联调接口。
- Warn和Error:Log Level切到Warn以上,用于全局巡检当前测试包有没有隐藏问题。
这么说可能有点抽象,举一个我最近调试的例子。一次线上崩溃,生产环境无法复现,测试机必须连Android Studio录日志。我设置了过滤器:包名+级别Warn以上+搜索关键字“Order”三个条件叠加,保留了一个多小时日志,最后定位到是某个第三方SDK在极低内存情况下空指针导致的。没有过滤条件,这个定位起码多花一倍时间。
注意:Android Studio的过滤逻辑是“同时满足”条件,条件越多,日志越少。搜索框支持正则后,能力扩展了很多。比如搜
(Fail|Error|Timeout)能同时匹配多个错误关键词。实际调试时,条件设得越精准,越不容易被无关海量日志干扰。
3.3 基础写法示例:一段规范的日志代码长什么样
上面讲了一堆理念,现在来点实际的。假设你写一个登录模块,规范的日志应该是这样:
public class LoginManager { private static final String TAG = "ZS_LoginManager"; public void login(String userName, String password) { Log.i(TAG, "login start, userName = " + userName); try { boolean result = doLoginRequest(userName, password); if (result) { Log.i(TAG, "login success, userName = " + userName); } else { Log.w(TAG, "login return false, userName = " + userName); } } catch (NetworkException e) { Log.e(TAG, "login failed, caused by network error", e); } } }几个细节:
- 登录流程的关键节点打Info,不打的级别少了点现场感,多了就变噪音。
- 失败但未崩溃的情况打Warn,因为它是潜在风险,但当前流程还能继续。
- 异常打Error,第三个参数传异常对象e,这样打印堆栈信息,方便后续定位。
- Tag带上了模块前缀“ZS_”,意味着后续可以用“ZS_”一把抓所有本业务模块日志。
这段代码没什么炫技,但它体现的是日志设计的思路:什么信息值得留,用什么级别留,留了之后别人怎么找到它。日志本质上是一份给未来的自己和其他开发者看的文档。
3.4 高级技巧:adb logcat命令行“真香”用法
Android Studio面板好用,但真实回归测试、自动化测试、性能测试场景下,它不够用。原因很简单:测试手机连电脑跑太麻烦,而且面板实时输出日志会导致IDE卡顿。这时候就要请出adb logcat。
打开终端,连接设备后执行:
# 查看所有日志 adb logcat # 按级别过滤,如只看Warning及以上 adb logcat *:W # 按Tag过滤 adb logcat -s ZS_LoginManager:V # 按Tag和级别组合过滤 adb logcat -s ZS_LoginManager:V ActivityManager:I # 清空当前日志缓冲区 adb logcat -c*:W的意思是“所有Tag的Warn以上级别日志”,-s是静默模式,后面按“Tag:级别”逐个指定。-c清空缓冲区很常用,在抓取前先清空一次,可以让日志文件里只保留你即将要复现问题的那段时间的记录,避免历史日志污染。
核心组合拳是配合进程过滤:
# 只看某个包名的进程日志 adb logcat --pid=$(pidof com.example.app)先把当前目标进程的PID拿到,再用--pid参数过滤,效果上相当于Android Studio面板里的“Show only selected application”。但这个命令在命令行里执行效率更高,适合脚本化。
保存到文件的用法:
# 保存日志到文件,Ctrl+C结束 adb logcat > app_log.txt # 或加上时间戳,每条日志前自动带有精确时间 adb logcat -v threadtime > app_log_with_time.txt # 同时打印到屏幕和文件 adb logcat | tee app_log.txt这里有个常见的坑:直接重定向>保存时,如果终端崩溃或者设备断开,文件可能没有结束符导致工具无法解析。我自己的做法是抓取结束先执行Ctrl+C,再diff一下文件大小确认非空。
3.5 应对日志量爆炸:logcat 4G缓冲区配置与按文件切割
回到标题里的“logcat 4g”这个热搜词。很多人想加大日志缓冲区,尤其是做长时间性能测试时,默认的缓冲区根本不够用。Android其实提供了设置日志缓冲区大小的配置项,前提是设备开启了开发者选项。
路径大致是:设置 -> 系统 -> 开发者选项 -> 日志记录器缓冲区大小,常见选项有64K、256K、1M、4M、16M甚至64M。不同厂商ROM可能名称略有不同。在开启开发者选项的前提下,把缓冲区调大到4M或者更大,可以减少日志被覆盖的频率。
但坦白说,调大缓冲区只是缓解,不是根治。长期跑测试的正确做法是用adb logcat重定向到文件,并且按文件大小自动切割。终端里可以用logrotate等工具,但日常开发中更常用的是脚本:
# 每1小时切割一次日志文件,并保留最近24个文件 adb logcat -v threadtime >> ./logs/app_$(date +%Y%m%d_%H).log配合cron或者后台脚本,就能实现按小时自动切片。我自己跑一晚上性能测试,用这招能拿完完整整的日志。另一个Excel式的技巧是:抓取日志同时记录当前系统时间,方便后续按精确时间点搜索。
提示:如果设备是Android 10及以上,可以通过
adb shell setprop persist.log.tag设置全局日志级别,比如-s V能看到非常详细的内核级信息。但生产环境手机不要随意改这些属性,只在专用测试设备上操作。
3.6 抓崩溃日志:bugreport与重启现场还原
崩溃日志是日志系统里最重要的部分,但很多同学只在Logcat面板里看到“FATAL EXCEPTION”这几个字,并不知道怎么把崩溃前后的完整上下文抓下来。这里要补充两个关键技术:一个是通过crash缓冲区分区查看崩溃日志;另一个是通过bugreport来抓取重启前后的系统状态。
如果只是当前进程崩溃,执行:
adb logcat -b crash会直接输出最近的历史崩溃记录。-b后面跟缓冲区分区名,可用的缓冲区包括main、system、crash、events等。我在排查“怎么看不到崩溃日志”的问题时,第一步就是先看main缓冲区里有没有FATAL EXCEPTION关键字,没有再看crash缓冲区。有些国产ROM会主动过滤崩溃堆栈,这时候crash缓冲区的数据反而更可靠。
如果是“重启后Logcat日志丢失,想知道重启前到底发生了什么”,则需要用bugreport。命令:
adb bugreport这条命令会把设备整个诊断信息打包成一个zip文件,里面有当前系统属性、进程列表、CPU负载、电池状态、Logcat的历史日志,以及关机前保存在dropbox里的系统级崩溃信息。bugreport生成的zip文件里有个关键路径,通常在FS/data/logs/或者FS/data/anr/下面,记录了重启前的关键系统日志。对于“设备重启后如何找到重启日志”这个问题,bugreport是目前最全面的官方手段。
不过bugreport包体很大,一般只用于系统级问题、重启问题、死机问题的排查,App内崩溃用logcat -b crash其实就够了。
3.7 Release包保留日志:BuildConfig与混淆映射的配合
还有一个高频场景:线上包崩溃了,怎么拿到日志?大多数release包默认不开启日志系统,因为混淆把Tag和类名扭曲了。这里面其实有标准解法:
- 第一步:在build.gradle的buildTypes中,给release类型配置
buildConfigField "boolean", "LOG_DEBUG", "true",让Release包保留日志输出能力(或通过多渠道包开关控制)。 - 第二步:App内集成一个日志采集库,比如自己封装一个LogHelper,内部判断DEBUG状态,把日志同时输出到Logcat和本地文件。
- 第三步:混淆规则里保留Tag字段名。
-keep class **.LogHelper { *; },让日志代码的类不会被混淆掉。 - 第四步:生成混淆映射文件mapping.txt(默认在build/outputs/mapping/release/下),遇到线上堆栈后,用retrace工具反混淆。
这套组合下来,线上包出了问题,通过用户反馈或者后台日志上报,你拿到的日志就是可读的。很多公司做崩溃平台,底层就是这套思路。如果你只是靠开发期在Android Studio里看日志,线上出了问题基本上只能靠猜。
4. 常见问题与排查技巧实录
4.1 高频问题速查表
实际用Logcat过程中,我遇到过的问题十有八九集中在下面这张表里:
| 症状 | 可能原因 | 解决思路 |
|---|---|---|
| Android Studio Logcat面板空空如也 | 设备选择错误、进程过滤错误、Log Level设太高 | 先确认设备和进程选择;再逐个降低Log Level |
| 日志打印了,但面板没有实时刷新 | 面板被暂停(Pause按钮),或IDE缓存卡住 | 点暂停按钮恢复;重启Logcat面板:关闭再重新打开 |
| 日志输出几秒就停了,之后什么也不出 | 缓冲区满了,日志被覆盖;或者进程被杀 | 清空缓冲区再抓:adb logcat -c;调大缓冲区大小 |
| 崩溃日志找不到 | Release包没开日志;崩溃发生在system进程里;ROM过滤 | 用logcat -b crash查看;上bugreport |
| adb logcat命令提示“device unauthorized” | 手机USB调试授权过期 | 拔掉USB线,重新插上,在手机上重新授权 |
| 日志里有大量“dalvikvm GC”刷屏 | 日志量太大,环境里的垃圾回收频繁 | 过滤Tag:adb logcat -s dalvikvm:! 反向排除 |
| 某些日志在Android Studio能看到,但adb下看不到 | 过滤条件、缓冲区不一致 | 明确指定buffer:adb logcat -b main -b crash |
每一条都是我实际碰到过的场景,输出来就是让你少踩坑。比如“设备选择错误”那条,看起来弱智,但它在团队里真的反复发生。人的大脑在处理复杂问题时,经常会忽略了界面角落里的一个极小下拉框。
4.2 日志被系统“裁剪”的隐性规则
很多同学不知道,Android系统为了保护隐私和性能,chmod了某些日志级别。比较常见的是:在Release模式下,默认不输出Log.d和Log.v(ProGuard/D8会通过assumenosideeffects直接删除对应代码),而系统面(system_server)的某些日志在普通App应用下是隐藏的,即使你加了权限也看不到全部。
如果开发时确实需要看系统级日志,有两个办法:
- 抓住设备的root权限或者用模拟器(模拟器一般不受限制),再adb logcat就能看到完整的系统日志。
- 用
adb shell dumpsys结合logcat -b system,把系统服务的关键信息一起看。
另一个容易忽略的是:Android 4.1以后Logcat对“非系统应用”输出做了限制,普通App无法读取其他App的日志,所以你在测试机上没法通过Logcat“偷看”另一个App的调试日志。这其实是为了隐私保护,但反过来也意味着:如果你自己的App崩溃了,只要进程里有日志,你一定能查;但如果崩溃的是别的App,你就没招了。
4.3 日志文件包含100个条目的真相与应对
再解释一下热搜里那条“日志文件包含100”:在Android的日志系统中,events缓冲区(事件日志)会维护一个历史文件列表,某些系统版本在非root状态下,默认只保留最近100个事件日志文件。如果超出了,旧文件会被自动删除。
这就带来一个实际问题:你明明想追查三天前某台设备上的事件,结果发现日志文件只剩最新100个。应对办法很简单:
- 及时归档。事件日志尽量定期拉取到本地,别指望设备上无限保存。
- 如果做专项测试,提前用
adb logcat -G 64M把事件缓冲区调大(-G参数可以直接设置缓冲区大小,单位是KB,64M就是65536K)。 - 对测试设备,用脚本定期把
/data/logs或/data/system/dropbox下的历史文件备份到电脑,防止被系统裁剪。
实际上,这个“100条”场景我还遇到过另一种变体:日志采集SDK在本地文件里默认只保留最近100个文件,超过的删掉。很多线上日志上报平台也是这个策略。如果你发现线上日志“断档”了,先检查是不是采集SDK的保留策略导致的,别急着怀疑网络上传失败。
4.4 独家技巧:如何快速从巨型日志里定位“案发现场”
抓日志容易,捞日志难。推荐一个我用了很久的方法:先宏观,再微观。
- 第一步,把日志文件里所有带时间戳的
FATAL EXCEPTION、ANR in、am_anr挑出来,如果只看一个进程的崩溃,先过滤进程号。 - 第二步,找到崩溃时间点,向后看几十行,看崩溃前最后执行的代码是什么。再向前看几十行,看崩溃前有什么异常迹象(比如OOM、内存警告、线程池耗尽)。
- 第三步,横向对比:如果同一个崩溃在不同设备出现,对比两台设备崩溃时间点前后日志内容,找出共同点——比如都是某个SDK初始化后崩溃,或者都在网络切换时崩溃。
具体命令上,我用Linux工具链配合:
# 找出所有崩溃时间点 grep -n "FATAL EXCEPTION\|AndroidRuntime" app_log.txt # 以崩溃时间戳为中心,截取前后100行 grep -A 100 -B 100 "2026-01-15 14:23:45" app_log.txt > crash_window.txt有时候海量日志里同一时间戳重复出现,你还得先统一时间格式。建议抓日志时用-v threadtime格式,这样每条日志都带线程ID和时间戳,后面用grep/awk/正则都好操作。
4.5 封装一个快上手的LogHelper
如果项目组还没有统一的日志方案,我给你一个可以直接用的LogHelper模板。它可以把日志同时输出到Logcat和本地文件,Release包也可以通过配置开启,遇到问题至少能拿到现场。
public class LogHelper { private static final String TAG_PREFIX = "ZS_"; private static boolean debug = BuildConfig.DEBUG; private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()); private static final String LOG_FILE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/AppLogs/"; private static final int MAX_LOG_FILE_SIZE = 5 * 1024 * 1024; // 5MB切割 public static void init(boolean isDebug) { debug = isDebug; } public static void d(String tag, String msg) { if (debug) { Log.d(TAG_PREFIX + tag, msg); writeToFile(TAG_PREFIX + tag, "D", msg); } } public static void e(String tag, String msg, Throwable tr) { Log.e(TAG_PREFIX + tag, msg, tr); writeToFile(TAG_PREFIX + tag, "E", msg + "\n" + Log.getStackTraceString(tr)); } private static void writeToFile(String tag, String level, String msg) { try { File dir = new File(LOG_FILE_DIR); if (!dir.exists()) { dir.mkdirs(); } File logFile = new File(dir, "app_log.log"); if (logFile.length() > MAX_LOG_FILE_SIZE) { File old = new File(dir, "app_log_" + System.currentTimeMillis() + ".log"); logFile.renameTo(old); } FileWriter fw = new FileWriter(logFile, true); String content = String.format("%s [%s] [%s] %s\n", TIME_FORMAT.format(new Date()), level, tag, msg); fw.write(content); fw.flush(); fw.close(); } catch (IOException e) { // 日志文件写入失败时,不再递归打日志,避免死循环 } } }这段代码我做了几层设计考量:
- 用LogHelper.e统一接收Throwable,这样堆栈信息能完整落到文件里。
- Release包初始化时传false,则不输出Debug级日志到Logcat,但Error级仍然保留,兼顾性能和现场。
- 本地文件超过5MB自动改名,防止单个文件过大打不开。
- 写文件失败时不再递归调用日志方法,避免死循环把Exception刷爆。
实际线上项目还能在这个基础上加“按天分目录”“定期上传崩溃日志到后台”等功能,不过对于个人项目和学习项目,这个程度已经够用了。
4.6 多人协作时的日志规范与命名习惯
最后单独讲一下工作场景里团队配合的日志规范。你可能觉得这跟技术关系不大,但经历过“同事日志Tag全是test”的人都知道,这直接关系到排查效率。
我们在项目组里定的约定很简单:
- Tag必须以业务模块名开头,比如“OrderModule_”“PayModule_”,配合LogHelper的TAG_PREFIX使用更佳。
- 关键业务动作(下单、支付回调、登录、登出)必须打Info日志。
- 所有catch分支必须打Error日志,并带上原始异常对象。
- 禁止在日志里打印明文密码、完整手机号、身份证号等敏感信息——这是合规底线,不只是风格问题。
- 提交代码前,用“adb logcat + 自己的Tag + Error级别”跑一遍冒烟,确认没有明显错误日志再提交。
说真的,一个项目如果日志规范执行得好,线上问题定位时间至少缩短一半。反过来说,如果日志乱写,你有再好的工具也很难排查,因为日志系统本身就是“现场记录员”,记录员不靠谱,侦探再有本事也没用。
作为长期做Android开发的人,我慢慢把Logcat从“调试工具”定位成“应用的手术记录仪”。写日志不是浪费时间,而是在为未来的自己留线索。我踩过太多“日志被冲掉”“Tag找不到”“Release没日志”的坑以后,才整理出上面这套习惯和方案。希望这篇文章能帮你避开同样的问题,把Logcat真正用起来,而不是打开面板看个热闹。