· 2 min read
logcat 原理解析:Android 日志是如何读取的
Android
每次运行
adb logcat时,背后发生了什么?从 shell 命令到日志展示,数据经历了怎样的流转?
当我们执行 adb logcat 时,实际上是在和 Android 的日志系统进行一场复杂的「接力赛」。理解这个过程,有助于更好地调试应用,也能更快定位问题根因。
sequenceDiagram
participant adb as adb server
participant shell as shell/logcat
participant socket as /dev/socket/logdr
participant logd as logd 守护进程
participant kernel as Kernel Buffer
adb->>shell: 建立连接
shell->>socket: 连接 logdr socket
socket->>logd: 转发请求
logd->>kernel: 读取日志缓冲区
kernel-->>logd: 返回日志数据
logd-->>socket: 日志数据
socket-->>shell: 日志数据
shell-->>adb: 输出到终端
在 /logcat/logcat.cpp 的 __logcat 方法中调用 android_logger_list_read
static int __logcat(android_logcat_context_internal* context) {
......
while (!context->stop &&
(!context->maxCount || (context->printCount < context->maxCount))) {
struct log_msg log_msg;
int ret = android_logger_list_read(logger_list, &log_msg);
if (!ret) {
logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
break;
}
......
android_logger_list_read是 liblog/logger_read.c 中的接口
LIBLOG_ABI_PUBLIC int android_logger_list_read(struct logger_list* logger_list,
struct log_msg* log_msg) {
struct android_log_transport_context* transp;
struct android_log_logger_list* logger_list_internal =
(struct android_log_logger_list*)logger_list;
int ret = init_transport_context(logger_list_internal);
if (ret < 0) {
return ret;
}
......
}
其中调用了init_transport_context,该接口同样位于 liblog/logger_read.c ,主要实现如下
static int init_transport_context(struct android_log_logger_list* logger_list) {
......
__android_log_lock();
/* mini __write_to_log_initialize() to populate transports */
if (list_empty(&__android_log_transport_read) &&
list_empty(&__android_log_persist_read)) {
__android_log_config_read();
}
__android_log_unlock();
......
}
其中调用了__android_log_config_read,位于 /liblog/config_read.c,主要实现如下
LIBLOG_HIDDEN void __android_log_config_read() {
......
#if (FAKE_LOG_DEVICE == 0)
if ((__android_log_transport == LOGGER_DEFAULT) ||
(__android_log_transport & LOGGER_LOGD)) {
extern struct android_log_transport_read logdLoggerRead;
extern struct android_log_transport_read pmsgLoggerRead;
__android_log_add_transport(&__android_log_transport_read, &logdLoggerRead);
__android_log_add_transport(&__android_log_persist_read, &pmsgLoggerRead);
}
#endif
}
其中调用了logdLoggerRead,位于 /liblog/logd_reader.c,主要实现如下
LIBLOG_HIDDEN struct android_log_transport_read logdLoggerRead = {
.node = { &logdLoggerRead.node, &logdLoggerRead.node },
.name = "logd",
.available = logdAvailable,
.version = logdVersion,
.read = logdRead,
.poll = logdPoll,
.close = logdClose,
.clear = logdClear,
.getSize = logdGetSize,
.setSize = logdSetSize,
.getReadableSize = logdGetReadableSize,
.getPrune = logdGetPrune,
.setPrune = logdSetPrune,
.getStats = logdGetStats,
};
其中通过logdRead函数指针为.read字段 进行赋值,logdRead的主要实现如下
static int logdRead(struct android_log_logger_list* logger_list,
struct android_log_transport_context* transp,
struct log_msg* log_msg) {
......
ret = logdOpen(logger_list, transp);
if (ret < 0) {
return ret;
}
......
ret = recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0);
......
}
里面调用了logdOpen来打开套接字logdr,而后返回logdRead进行recv。
static int logdOpen(struct android_log_logger_list* logger_list,
struct android_log_transport_context* transp) {
......
sock = socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_SEQPACKET);
......
return sock;
}
总结
| 层级 | 组件 | 职责 |
|---|---|---|
| 应用层 | logcat | 命令行工具,负责解析参数和格式化输出 |
| 传输层 | liblog | 提供统一的日志读取接口 |
| 通信层 | socket (logdr) | 通过 Unix Domain Socket 与 logd 通信 |
| 守护进程 | logd | 管理日志缓冲区,处理读写请求 |
| 内核层 | logger driver | 环形缓冲区,存储日志条目 |
整个流程的关键在于
/dev/socket/logd这个 Unix Domain Socket。logcat 通过它向 logd 发送请求,logd 再从内核的日志缓冲区中读取数据返回。