How logcat Works: Reading Android Logs
Every time you run
adb logcat, what happens behind the scenes? What path does the data take from the shell command to the log display?
When we execute adb logcat, we are actually engaged in a complex “relay race” with Android’s logging system. Understanding this process helps with debugging applications and locating root causes faster.
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: 输出到终端
The __logcat method in /logcat/logcat.cpp calls 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 is an interface in 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;
}
......
}
android_logger_list_read calls init_transport_context, also defined in 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();
......
}
init_transport_context calls __android_log_config_read, defined in /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
}
__android_log_config_read registers logdLoggerRead, which is defined in /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,
};
The .read field points to logdRead. Its main implementation is:
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);
......
}
logdRead calls logdOpen to open the logdr socket, then reads from it with 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;
}
Summary
| Layer | Component | Responsibility |
|---|---|---|
| Application | logcat | Command-line tool that parses arguments and formats output |
| Transport | liblog | Provides a unified log reading interface |
| Communication | socket (logdr) | Communicates with logd via Unix Domain Socket |
| Daemon | logd | Manages log buffers and handles read/write requests |
| Kernel | logger driver | Ring buffer that stores log entries |
The key to the entire flow is the Unix domain socket at
/dev/socket/logdr. logcat sends requests through this socket, and logd reads data from the kernel log buffer and returns it.