Back to Blog
By AriesZhou · · 3 min read

How logcat Works: Reading Android Logs

Android

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

LayerComponentResponsibility
ApplicationlogcatCommand-line tool that parses arguments and formats output
TransportliblogProvides a unified log reading interface
Communicationsocket (logdr)Communicates with logd via Unix Domain Socket
DaemonlogdManages log buffers and handles read/write requests
Kernellogger driverRing 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.