这篇文章给大家聊聊关于深入解析Android系统日志管理,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。
1.概述
在上一节中我们查看了logd和logcat的说明。本节我们来看看Android日志系统架构以及logdlogcat的初始化操作。
2.架构
2.1 读写日志架构
在Android 5.0之前,日志由内核的环形缓冲区保存。 Android 5.0以后,日志保存在用户空间,通过Socket访问。 Android 5.0之后引入了logd守护进程来读写日志。
无论是应用层还是Native层,日志的读写都是通过liblog提供的接口,访问logd的两个socket缓冲区:logdr和logdw来实现的。图片来自CSDN-私房菜:
日志系统2-1.PNG
2.2 写日志流程
在应用层,可以通过android.util.Log、android.util.SLog、android.util.EventLog接口将日志写入main、system、event的不同缓冲区。如果想用Java调用日志,需要导入以下内容:
导入android.util.Log;
导入android.util.SLog;
导入android.util.EventLog;(1)应用层写日志方法如下:日志系统2-2.PNG 在Native C/C++中,进程加载liblog.so并调用ALOGD()和ALOGI()写入日志,最后将日志通过logd写入logdw套接字。
如果想在Native中调用liblog的内容,需要在Android.mk或Android.bp中添加liblog并引入头文件:#include。
(2)Native层写日志方法如下:日志系统2-3.PNG
2.3 读日志流程
在Android中,日志主要是通过logcat进程来读取的。 Logcat 是一个本机C 进程。通过加载liblog,调用logd的读取接口来读取logdr套接字的日志内容。
日志系统2-4.PNG
3.源码分析
Android系统日志主要需要关注三个部分:
logd守护进程:日志系统的大管家,管理三个日志套接字:logd、logdr、logdw;
logcat进程:日志读取工具;
liblog:提供日志读取、写入、过滤等接口,供logcat、Java、Native等程序使用。
3.1 logd启动及初始化
3.1.1 启动logd
Android系统启动后,init进程会解析logd.rc并启动logd服务,如下:
//系统/核心/logd/logd.rc
服务日志/系统/bin/logd
套接字logd 流0666 logd logd
套接字logdr seqpacket 0666 logd logd
套接字logdw dgram+passcred 0222 logd logd
文件/proc/kmsg r
文件/dev/kmsg w
用户登录
组logd 系统package_info readproc
功能SYSLOG AUDIT_CONTROL
优先级10
writepid /dev/cpuset/system-background/tasks 从上面的服务可以看出,启动了一个名为logd的守护进程,该进程存放在手机的/system/bin中。同时创建并启动三个套接字:
logd:接收logcat传递过来的指令并进行处理,如logcat -g、logcat -wrap等;
logdr:logcat从此缓冲区读取缓冲区;
logdw:日志写入缓冲区。
(1)logd初始化调用栈如下:日志系统2-5.png(2)logd的初始化流程:打开/dev/kmsg读取内核日志并通过LogKlog存储;
如果配置了“ro.logd.kernel”属性,则打开/proc/kmsg读取内核日志;
设置运行时优先级和权限;
启动重新初始化线程。当logd-reinit传入参数reinit时,就会被调用。 Reinit仅在机器上电时启动一次;
启动每个日志侦听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit 和LogKlog。
(3)源码//系统/核心/logd/main.cpp
int main(int argc, char* argv[]) {
//logd 是假设时区是UTC 写入的。
//如果未设置TZ,则在某些时间实用程序libc 函数(包括mktime)中查找persist.sys.timezone。
//它混淆了logd 时间处理,因此这里TZ 显式设置为UTC,这会覆盖该属性。
setenv("TZ", "UTC", 1);
//发出重新初始化命令。 KISS 参数解析。
if ((argc 1) argv[1] !strcmp(argv[1], "--reinit")) {
返回问题Reinit();
}
//1.打开/dev/kmsg读取内核日志并通过LogKlog存储
static const char dev_kmsg[]="/dev/kmsg";
fdDmesg=android_get_control_file(dev_kmsg);
如果(fdDmesg 0){
fdDmesg=TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
//2.如果配置了“ro.logd.kernel”属性,则打开/proc/kmsg读取内核日志
int fdPmesg=-1;
布尔klogd=__android_logger_property_get_bool(
"ro.logd.kernel",
BOOL_DEFAULT_TRUE | | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
如果(klogd){
static const char proc_kmsg[]="/proc/kmsg";
fdPmesg=android_get_control_file(proc_kmsg);
如果(fdPmesg 0){
fdPmesg=TEMP_FAILURE_RETRY(
打开(proc_kmsg,O_RDONLY | O_NDELAY | O_CLOEXEC));
}
if (fdPmesg 0) android:prdebug("无法打开%sn", proc_kmsg);
}
//3.设置运行时优先级和权限
boolauditd=__android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
如果(drop_privs(klogd,auditd)!=0){
返回EXIT_FAILURE;
}
//4.启动重新初始化线程。当logd-reinit传入参数reinit时,就会被调用。 Reinit 仅在计算机开启时启动一次。
sem_init(重新初始化, 0, 0);
pthread_attr_t 属性;
if (!pthread_attr_init(attr)) {
struct sched_param 参数;
memset(参数, 0, sizeof(参数));
pthread_attr_setschedparam(attr, 参数);
pthread_attr_setschedpolicy(attr, SCHED_BATCH);
if (!pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED)) {
pthread_t 线程;
重新初始化运行=真;
if (pthread_create(线程, attr, reinit_thread_start, nullptr)) {
重新初始化运行=假;
}
}
pthread_attr_destroy(attr);
}
//用于管理在SOCKET连接上读取的最后一次日志时间,并作为一系列日志条目的读取器锁。
LastLogTimes* 次=new LastLogTimes();
//5.启动每个日志监听器
//5.1 首先创建一个LogBuffer对象。 LogBuffer是负责保存所有日志项的对象。
logBuf=new LogBuffer(次);
信号(SIGHUP,reinit_signal_handler);
如果(__android_logger_property_get_bool(
"logd.statistics", BOOL_DEFAULT_TRUE | "logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST | BOOL_DEFAULT_FLAG_PERSIST |
BOOL_DEFAULT_FLAG_ENG |
BOOL_DEFAULT_FLAG_SVELTE)) {
logBuf-enableStatistics();
}
//5.2 LogReader监听/dev/socket/logdr。当客户端连接时,例如logcat,日志缓冲区中的日志条目将被写入客户端。
LogReader* reader=new LogReader(logBuf);
if (reader-startListener()) {
返回EXIT_FAILURE;
}
//5.3 LogListener监听/dev/socket/logdw上客户端启动的日志消息,监听是否有日志写入。
LogListener* swl=new LogListener(logBuf, reader);
//Backlog 和/proc/sys/net/unix/max_dgram_qlen 设置为大值
如果(swl-startListener(600)){
返回EXIT_FAILURE;
}
//5.4 CommandListener监听/dev/socket/logd上传入的logd命令,即监听是否有命令发送到logd
CommandListener* cl=new CommandListener(logBuf, reader, swl);
if (cl-startListener()) {
返回EXIT_FAILURE;
}
//5.5 如果配置了ro.logd.auditd属性,则启动LogAudit。 LogAudit 在NETLINK_AUDIT 套接字上监听selinux 启动的日志消息。
LogAudit* al=nullptr;
如果(审计){
al=new LogAudit(logBuf, 读者,
__android_logger_property_get_bool(
"ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
? FD消息
: -1);
}
//5.6 如果配置了ro.logd.kernel属性,则启动LogKlog存储内核日志。
LogKlog* kl=nullptr;
如果(klogd){
kl=new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al !=nullptr);
}
//5.7 通过LogAudit和LogKlog分别读取selinux和kernel日志
readDmesg(al, kl);
//失败是一个选项.消息位于dmesg 中(标准要求)
if (kl kl-startListener()) {
删除kl;
}
if (al-startListener()) {
删除所有;
}
TEMP_FAILURE_RETRY(暂停());
返回退出_成功;
}main函数中默认创建了四个对象LogBuffer、LogReader、LogListener和CommandListener:
LogBuffer:LogBuffer是负责保存所有日志项的对象;
LogReader:LogReader 监听/dev/socket/logdr。当客户端连接时,例如logcat,日志缓冲区中的日志条目将被写入客户端;
LogListener:LogListener监听客户端在/dev/socket/logdw上启动的日志消息,监听是否有日志写入;
CommandListener:CommandListener监听/dev/socket/logd上传入的logd命令,即监听是否有命令发送到logd。
另外,还有两个对象LogAudit和LogKlog,由属性控制:
LogAudit:由属性"ro.logd.auditd"控制,在NETLINK_AUDIT套接字上监听selinux发起的日志消息,新的日志条目将被添加到LogBuffer中,并且LogReader将被通知向连接的发送更新客户;
LogKlog:由属性"ro.logd.kernel"控制,用于存储内核日志。内核日志通过“/dev/kmsg”、“/proc/kmsg”获取。
3.1.2 启动logd-reinit
(1)logd.rc中启动logd-reinit如下:服务logd-reinit /system/bin/logd --reinit
一次性
残疾人
用户登录
组日志
writepid /dev/cpuset/system-background/tasks 启动logd-reinit 服务。主要任务是重新初始化logd的LogBuffer。上面的启动脚本中,配置为oneshot,即开机时只执行一次。通过上面logd的初始化,可以看到logd启动后,创建了一个线程reinit_thread_start()。当logd-reinit传入参数reinit时,函数就会被执行。
logd-reinit 分两步:
如果reinit启动并且/deg/kmsg打开成功,则将logd.daemon: renit写入kmsg;
重新初始化每个日志缓冲区的大小并初始化其他参数,但不会重新生成LogBuffer对象。
(2)源码//系统/核心/logd/main.cpp
静态void* reinit_thread_start(void* /*obj*/) {
prctl(PR_SET_NAME, "logd.daemon");
while (reinit_running!sem_wait(reinit) reinit_running) {
如果(fdDmesg=0){
static const char reinit_message[]={ KMSG_PRIORITY(LOG_INFO),
"l",
"哦",
"g",
"d",
".",
"d",
"一个",
"e",
"我",
"哦",
"n",
":",
" ",
"r",
"e",
"我",
"n",
"我",
"t",
"n" };
写(fdDmesg,reinit_message,sizeof(reinit_message));
}
//任何读取的内容都会持久化//重新初始化每个日志缓冲区的大小并初始化其他参数,但不会重新生成LogBuffer对象
如果(logBuf){
logBuf-init();
logBuf-initPrune(nullptr);
}
android:ReReadEventLogTags();
}
返回空指针;
}
3.1.3 启动logd-auditctl
(1)logd.rc中启动logd-auditctl# 将SELinux 拒绝生成限制为5 次/秒
服务logd-auditctl /system/bin/auditctl -r 5
一次性
残疾人
用户登录
组日志
功能AUDIT_CONTROL logd-auditctl 的主体是/system/bin/auditctl。在logd的android.bp中,通过编译auditctl.cpp得到,并加载了liblogd库。 logd-auditctl是Android 10.0中引入的新功能,旨在将selinux denia的日志打印限制为每5秒一次。
(2)Android.bp中auditctl展示如下:cc_binary {
name: "auditctl",
srcs: ["auditctl.cpp"],
静态库: [
"liblogd",
],
Shared_libs: ["libbase"],
cflags: [
"-墙",
"-维克斯特拉",
"-错误",
"-Wconversion"
],
}(3)logd-auditctl初始化调用栈如下:日志系统2-6.png(4)源码logd-auditctl 的主要功能是限制selinux denia 的日志打印频率为每5 秒一次。 logd-auditctl在logd.rc中配置,传入参数为-r 5,限制selinux日志写入频率为5秒。
//系统/core/logd/auditctl.cpp
int main(int argc, char* argv[]) {
uint32_t 速率=0;
布尔更新率=假;
int 选择;
//如果logd-auditctl传递了-r参数,则获取该参数的值
//即这里的rate为5,标记update_rate为startup
while ((opt=getopt(argc, argv, "r:")) !=-1) {
开关(选择){
案例“r”:
如果(!android:base:ParseUint(optarg,速率)){
error(EXIT_FAILURE, errno, "无效率");
}
更新率=真;
休息;
默认: /* "?" */
用法(argv[0]);
退出(EXIT_FAILURE);
}
}
//将来我们可能会在auditctl中添加其他选项
//所以这个if 语句将会扩展。
//if (!update_rate !update_backlog !update_whatever) .
如果(!更新率){
fprintf(stderr, "无事可做n");
用法(argv[0]);
退出(EXIT_FAILURE);
}
//如果传入-r参数,则更新速率
如果(更新率){
do_update_rate(速率);
}
返回0;
}do_update_rate:创建协议号为NETLINK_AUDIT的netlink套接字,并通过audit_rate_limit发送selinux频率。
//系统/core/logd/auditctl.cpp
静态无效do_update_rate(uint32_t 速率) {
//创建套接字PF_NETLINK
int fd=audit_open();
如果(fd==-1){
错误(EXIT_FAILURE,errno,"无法打开审核套接字");
}
int 结果=audit_rate_limit(fd, 速率);
关闭(fd);
如果(结果0){
fprintf(stderr, "无法更新审核率限制: %dn", result);
退出(EXIT_FAILURE);
}
}audit_rate_limit:组装结构体audit_status,传入频率为5秒,最后通过sendto()将消息发送到内核,从用户态切换到内核态。
//系统/核心/logd/libaudit.c
intaudit_rate_limit(int fd,uint32_t限制){
结构审计状态状态;
memset(状态, 0, sizeof(状态));
status.mask=AUDIT_STATUS_RATE_LIMIT;
status.rate_limit=限制; /* 每秒审核条目*/
返回audit_send(fd,AUDIT_SET,状态,sizeof(状态));
}
3.2 logcat启动
编译logcat时,会编译两个进程/system/bin/logcat和/system/bin/logcatd。和logd一样,当logcat进程启动时,init进程会解析logcatd.rc进行加载。 logcatd.rc 看起来像这样:
服务logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v 可打印-D -f /data/misc/logd/logcat -r ${logd.logpersistd .rotate_kbytes:-1024} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
类Late_Start
残疾人
# logd 用于写入/data/misc/logd,日志组用于从日志守护进程读取
用户登录
群组日志
writepid /dev/cpuset/系统背景/任务
oom_score_adjust -600 从上面的服务可以看出,启动了一个名为logcatd的守护进程,该进程存放在手机的/system/bin中。启动logcatd时,传入-b-v-f等参数。
logcat启动后,首先创建上下文,设置信号量,然后启动一个while循环来接收logcat命令。
int main(int argc, char** argv, char** envp) {
android_logcat_context ctx=create_android_logcat();
if (!ctx) 返回-1;
信号(SIGPIPE,退出);
int retval=android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
int ret=android_logcat_destroy(ctx);
if (!ret) ret=retval;
返回ret;
}android_logcat_run_command()用于解析logcat传入的命令,最后在函数__logcat()中启动一个while无限循环来执行logcat传入的各种命令。
int android_logcat_run_command(android_logcat_context ctx,
int 输出,int 错误,
int argc, char* const* argv,
字符* const* envp) {
android_logcat_context_internal* 上下文=ctx;
上下文输出_fd=输出;
上下文错误_fd=错误;
上下文argc=argc;
上下文argv=argv;
上下文envp=envp;
上下文停止=false;
上下文线程_stopped=false;
返回__logcat(上下文);
【深入解析Android系统日志管理】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
想要深入了解 Android App 的运行情况,日志系统真是太必要了!
有12位网友表示赞同!
每次遇到 app 崩溃都不知道哪里出问题,日志系统就能帮我找到关键信息。
有10位网友表示赞同!
学习 Android 开发需要好好研究一下它的日志系统吧,功能还挺强大的。
有6位网友表示赞同!
使用合适的日志工具真的可以省下不少debug的时间。
有16位网友表示赞同!
Android 日志系统记录的信息挺详细的,能帮助排查各种问题。
有11位网友表示赞同!
看来开发 App 的时候要学会善用 Android 日志系统的功能啊。
有16位网友表示赞同!
很多博客文章都会讲解如何使用 Android 日志系统进行调试,可以参考一下。
有10位网友表示赞同!
之前没太注意日志系统,现在知道它真的很重要了!
有10位网友表示赞同!
Android 日志系统是开发者的好帮手,可以帮助我们快速定位问题。
有9位网友表示赞同!
想深入学习 Android 开发,对日志系统的掌握至关重要。
有9位网友表示赞同!
使用合适的日志记录策略能提高代码可维护性和调试效率。
有13位网友表示赞同!
学习过一些基本的日志系统知识,感觉对开发有一定帮助!
有5位网友表示赞同!
Android 日志系统可以记录各种事件信息,非常实用!
有15位网友表示赞同!
想要成为一名优秀 Android 开发者,应该深入了解其日志系统的运作机制。
有14位网友表示赞同!
在 App 的后期维护中,日志系统也是一个不可或缺的工具!
有12位网友表示赞同!
Android 日志系统可以记录各种异常信息,帮助我们更好地 understand app 运行情况。
有12位网友表示赞同!
学习 Android 开发应该从基础入手,包括对日志系统的了解。
有17位网友表示赞同!
使用清晰简洁的日志格式能够提高阅读性和调试效率!
有6位网友表示赞同!