Back to Blog
· 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 再从内核的日志缓冲区中读取数据返回。


相关阅读