Android 平台日志系统整体框架

这是一个介绍 Android 平台日志系统的系列文章:

  • Android 平台日志系统整体框架 (本文)
  • logd 守护进程初始化过程
  • 客户端写日志过程分析
  • logd 写日志过程分析
  • 冗余日志处理过程分析
  • logcat 读日志过程分析
  • logd 读日志过程分析
  • 内核日志处理分析
  • SeLinux 日志处理分析

本文基于 AOSP android-10.0.0_r41 版本讲解

1. 日志的类别与基本使用

在 Android 系统中,常见的日志有 5 类,main、radio、events、system、crash:

main 日志

main 日志是应用层 App 唯一可用的日志类型。

在 Java 层提供了 android.util.Log 接口来写 main 日志:

java 复制代码
// 写 VERBOSE 等级的日志
Log.v("YOUR TAG", "Message body");
// 写 DEBUG 等级的日志
Log.d("YOUR TAG", "Message body");
// 写 INFO 等级的日志
Log.i("YOUR TAG", "Message body");
// 写 WARN 等级的日志
Log.w("YOUR TAG", "Message body");
// 写 ERROR 等级的日志
Log.e("YOUR TAG", "Message body");

android.util.Log 接口提供了 5 个方法来写 5 个级别(Level)的日志,日志级别分别为 VERBOSE、DEBUG、INFO、WARN、ERROR、ASSERT,日志级别依次提升:

  • VERBOSE 级别日志:开发调试中的一些详细信息,仅在开发中使用,不可在发布产品中输出,不是很常见,包含诸如方法名,变量值之类的信息
  • DEBUG 级别日志:用于调试的信息,可以在发布产品中关闭,比较常见,开发中经常选择输出此种级别的日志,有时在 beta 版应用中出现
  • INFO 级别日志:该等级日志显示运行状态信息,可在产品出现问题时提供帮助,从该级别开始的日志通常包含完整的文本信息和调试信息,是最常见的日志级别
  • WARN 级别日志:运行出现异常即将发生错误或表明已发生非致命性错误,该级别日志通常显示出执行过程中的意外情况,例如将 try-catch 语句块中的异常打印堆栈轨迹之后可输出此种级别日志
  • ERROR 级别日志:已经出现可影响运行的错误,比如应用 crash 时输出的日志

在 native 层提供了 __android_log_print 函数来写 main 日志。习惯上,会自定义一些宏来简化 __android_log_print 函数的使用:

cpp 复制代码
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

与 Java 层类似,在 Natvie 层也有 5 个级别(Level)的日志输出函数。

radio 日志

radio 日志主要用于记录无线装置/电话相关系统应用的日志,可以调用 android.telephony.Rlog 打印日志。

events 日志

events 日志是用来诊断系统问题的。在应用框架提供了 android.util.EventLog 接口写日志,native 层提供了宏 LOG_EVENT_INT、LOG_EVENT_LONG、LOG_EVENT_FLOAT、LOG_EVENT_STRING 用来写入 events 类型日志。

system 日志

system 日志主要用于记录系统应用的日志信息,在 Java 层提供了 android.util.SLog 接口写入日志, native 层提供了 SLOGV、SLOGD、SLOGI、SLOGW、SLOGE 等宏用来写入 system 类型的日志。

crash 日志

  • crash 日志通常是程序 crash 时,记录的日志类型, Java 层使用 Log.println() (第一个参数设置为 LOG_ID_CRASH)打印 crash 类型的日志

了解了如何写入日志,接下来我们看看如何读取日志。

通常使用 logcat 命令查看日志:

bash 复制代码
logcat -b + 参数

可选的参数主要有:

  • all:查看所有缓冲区日志
  • default:查看 main、system、crash 三个类型日志信息
  • main:查看 main 类型日志
  • radio:查看 radio 类型日志
  • events:查看 events 类型日志
  • system:查看 system 类型日志
  • crash:查看 crash 类型日志

比如:

bash 复制代码
logcat -b main
# 用来查看 main 和 system 类型日志
logcat -b main,system
logcat -b all #查看所有日志

2.Kernel 层的日志

Kernel 层通常使用 printk 函数来记录日志,具体用法如下:

bash 复制代码
// KERN_INFO为日志级别,"Message body\n"则为日志信息
printk(KERN_INFO "Message body\n"); 

kernel 日志级别分别是:KERN_EMERG,KERN_ALERT,KERN_CRIT,KERN_ERR,KERN_WARNING,KERN_NOTICE,KERN_INFO,KERN_DEBUG

这些级别定义在内核源码中的 kern_levels.h 文件中:

cpp 复制代码
#define KERN_EMERGKERN_SOH "0"/* system is unusable */

#define KERN_ALERTKERN_SOH "1"/* action must be taken immediately */

#define KERN_CRITKERN_SOH "2"/* critical conditions */

#define KERN_ERRKERN_SOH "3"/* error conditions */

#define KERN_WARNINGKERN_SOH "4"/* warning conditions */

#define KERN_NOTICEKERN_SOH "5"/* normal but significant condition */

#define KERN_INFOKERN_SOH "6"/* informational */

#define KERN_DEBUGKERN_SOH "7"/* debug-level messages */

Kernel 日志输出到 /proc/kmsg 文件,用户可以通过读取这个文件来获取 kernel 日志:

bash 复制代码
cat /proc/kmsg

3. 日志系统整体框架

图片来自Android logd日志简介及典型案例分析,稍作修改。

系统启动时,init 进程会启动 logd 进程,logd 进程会启动多个子线程,我们主要关注其中的三个:

  • LogListener,启动一个 Unix Domain Socket 服务端,socket 服务端对应的文件是 /dev/socket/logdw,用于接受 App 发送的 socket 远程请求,向 LogBuffer 写入日志
  • LogReader,启动一个 Unix Domain Socket 服务端,socket 服务端对应的文件是 /dev/socket/logdr,用于接受 logcat 命令发送的 socket 远程请求,从 LogBuffer 中读出日志
  • CommandListener,启动一个 Unix Domain Socket 服务端,socket 服务端对应的文件是 /dev/socket/logd,用于接受 logcat 命令发送的 socket 远程请求,向 LogBuffer 发送管理命令

App 端写入日志时,最终都是调用到 __android_log_write 函数,通过 socket 远程调用,向 LogListener 发送消息,LogListener 读出 socket 消息,在写入 LogBuffer中

调用 logcat 命令时,最终都是调用到 __android_logger_readSendLogdControlMessage 函数,前者通过 socket 远程调用,向 LogReader 发送消息,LogReader 读出 socket 消息,接着从 LogBuffer 中读出日志信息;后者通过 socket 远程调用,向 CommandListener 发送消息,LogReader 读出 socket 消息,接着向 LogBuffer 发送管理命令

参考资料

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
前端赵哈哈18 分钟前
初学者入门:Android 实现 Tab 点击切换(TabLayout + ViewPager2)
android·java·android studio
一条上岸小咸鱼4 小时前
Kotlin 控制流(二):返回和跳转
android·kotlin
Jasonakeke4 小时前
【重学 MySQL】九十二、 MySQL8 密码强度评估与配置指南
android·数据库·mysql
Mertrix_ITCH4 小时前
在 Android Studio 中修改 APK 启动图标(2025826)
android·ide·android studio
荏苒追寻4 小时前
Android OpenGL基础1——常用概念及方法解释
android
人生游戏牛马NPC1号4 小时前
学习 Android (十七) 学习 OpenCV (二)
android·opencv·学习
恋猫de小郭5 小时前
谷歌开启 Android 开发者身份验证,明年可能开始禁止“未经验证”应用的侧载,要求所有开发者向谷歌表明身份
android·前端·flutter
用户095 小时前
Gradle声明式构建总结
android
用户095 小时前
Gradle插件开发实践总结
android
Digitally16 小时前
如何将视频从安卓设备传输到Mac?
android·macos