10 分钟阅读
-- 次浏览
Android Log 全链路: 从 Log.d() 到 logcat 输出

每个安卓开发者肯定都写过 Log.d(TAG, "hello")
但这行代码执行之后到底发生了什么? 日志是怎么从你的 App 进程跑到 logcat 的终端输出的?

在这篇文章中, 我将带你从 AOSP 源码出发, 完整追踪一条日志的生命周期.

全局视角

先给一张全链路的架构图:

graph TD
    subgraph app ["App 进程"]
        A["Log.d()"] --> B["println_native"]
        B --> C["liblog"]
        C --> D["LogdWrite()"]
        C --> E["PmsgWrite()"]
    end

    D -->|"logdw"| F
    E --> K[("pstore")]

    subgraph logd ["logd 进程"]
        F["LogListener"] --> G["SerializedLogBuffer"]
        G --> H["LogReader"]
    end

    H -->|"logdr"| I

    subgraph lcat ["logcat 进程"]
        I["LogdRead()"] --> J["格式化输出"]
    end

整条链路横跨了三个进程, 并涉及到 Java 和 Native 两层, 通过 Unix Domain Socket 进行 IPC.
下面让我们逐层拆解.

Layer 1: Java API — android.util.Log

入口在 Log.java1.

public static int d(@Nullable String tag, @NonNull String msg) {
    return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}

所有的 Log.v/d/i/w/e 方法都是 one-liner, 最终调用同一个 native 方法:

public static native int println_native(int bufID, int priority, String tag, String msg);

几个关键点:

  • LOG_ID_MAIN = 0 — 普通应用日志写入 main buffer
  • 除 main 外还有 LOG_ID_RADIO(1), LOG_ID_EVENTS(2), LOG_ID_SYSTEM(3), LOG_ID_CRASH(4) 等 buffer
  • Throwable 参数的重载版本会走 printlns(), 内部用 LineBreakBufferedWriter 分段写入, 避免超长堆栈被截断

Layer 2: JNI — android_util_Log.cpp

android_util_Log.cpp2 负责 Java -> Native 的桥接:

static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

JNI 层做的事情非常的简单:

  1. 把 Java String 转成 C 字符串 (通过 GetStringUTFChars)
  2. 调用 liblog 的 __android_log_buf_write()
  3. 释放字符串引用

JNI 方法注册表:

static const JNINativeMethod gMethods[] = {
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I",
                         (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I",
                         (void*) android_util_Log_logger_entry_max_payload_native },
};

Layer 3: liblog — 日志写入引擎

这一层是整个日志系统的核心.

3.1 入口: __android_log_buf_write

logger_write.cpp3:

int __android_log_buf_write(int log_id, int prio, const char* tag, const char* msg) {
  // class ErrnoRestorer { ErrnoRestorer() : saved_errno_(errno) {} ~ErrnoRestorer() { errno = saved_errno_; } };
  // 构造时保存当前 errno, 析构时恢复 -- 防止日志写入过程中意外修改调用者的 errno
  ErrnoRestorer errno_restorer;

  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
    return -EPERM;
  }

  __android_log_message log_message = {
      sizeof(__android_log_message), log_id, prio, tag, nullptr, 0, msg};
  __android_log_write_log_message(&log_message);
  return 1;
}

首先检查 isLoggable — 这就是 setprop log.tag.YourTag DEBUG 能控制日志级别的原因.

通过检查后, 构造 __android_log_message 结构体, 交给 __android_log_write_log_message().

3.2 logger_function: 可插拔的日志后端

#ifdef __ANDROID__
static __android_logger_function logger_function = __android_log_logd_logger;
#else
static __android_logger_function logger_function = __android_log_stderr_logger;
#endif
  • Android 设备上, 默认的 logger 是 __android_log_logd_logger, 把日志发给 logd
  • 非 Android 环境 (host 工具) 则直接输出到 stderr
  • 这个函数指针可以通过 __android_log_set_logger() 替换, 提供了扩展性

3.3 写入 logd: LogdWrite

__android_log_logd_logger 会将日志数据组装成 iovec 数组:

void __android_log_logd_logger(const struct __android_log_message* log_message) {
  struct iovec vec[3];
  vec[0].iov_base = &log_message->priority;   // 1 字节: 优先级
  vec[0].iov_len = 1;
  vec[1].iov_base = log_message->tag;          // tag + '\0'
  vec[1].iov_len = strlen(log_message->tag) + 1;
  vec[2].iov_base = log_message->message;      // message + '\0'
  vec[2].iov_len = strlen(log_message->message) + 1;

  write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
}

write_to_log() 做两件事:

static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
  int ret = LogdWrite(log_id, timestamp, vec, nr);  // 发给 logd
  PmsgWrite(log_id, timestamp, vec, nr);             // 写入 pstore
  return ret;
}

logd_writer.cpp4 中的 LogdWrite() 是真正的 IPC 发送端:

int LogdWrite(log_id_t logId, const struct timespec* ts,
              const struct iovec* vec, size_t nr) {
  // 构造 header
  android_log_header_t header;
  header.tid = gettid();
  header.realtime.tv_sec = ts->tv_sec;
  header.realtime.tv_nsec = ts->tv_nsec;
  header.id = logId;

  // header + payload 组装成新的 iovec
  newVec[0].iov_base = &header;
  newVec[0].iov_len = sizeof(header);
  // ... 拷贝原始 vec ...

  // 通过 Unix Domain Socket 发送
  // #define TEMP_FAILURE_RETRY(exp) ({ do { _rc = (exp); } while (_rc == -1 && errno == EINTR); _rc; })
  // 系统调用被信号中断 (EINTR) 时自动重试, 直到返回非 -1 或遇到其他错误
  ret = TEMP_FAILURE_RETRY(writev(logd_socket.sock(), newVec, i));

  // EAGAIN 说明 logd 过载, 其他错误则重连
  if (ret < 0 && errno != EAGAIN) {
    logd_socket.Reconnect();
    ret = TEMP_FAILURE_RETRY(writev(logd_socket.sock(), newVec, i));
  }
  return ret;
}

Socket 的细节:

  • 连接 /dev/socket/logdw (UNIX Domain, SOCK_DGRAM)
  • 普通日志用 NonBlockingSocket — 写入不阻塞, logd 过载时直接丢弃 (计入 dropped 计数器)
  • Security 日志用 BlockingSocket — 确保安全日志不丢失
  • 发送失败时会自动 reconnect 并重试一次

3.4 旁路: PmsgWrite — pstore 持久化5

int PmsgWrite(log_id_t logId, const struct timespec* ts,
              const struct iovec* vec, size_t nr) {
  // 非 debuggable 设备只写 events 和 security
  if (!ANDROID_DEBUGGABLE) {
    if (logId != LOG_ID_EVENTS && logId != LOG_ID_SECURITY) {
      return -1;
    }
  }
  // 写入 /dev/pmsg0
  ret = TEMP_FAILURE_RETRY(writev(pmsg_fd, newVec, i));
  return ret;
}

/dev/pmsg0 是 Linux pstore (persistent store) 的接口, 日志写入后即使设备异常重启也能保留. 这就是 “last kmsg” 等崩溃日志能在重启后被恢复的原因.

注意在 release 版本上, 只有 events 和 security 日志会写入 pstore.

Layer 4: logd — 日志守护进程

logd 是一个由 init 启动的 native daemon.

4.1 启动: main.cpp6

int main(int argc, char* argv[]) {
    signal(SIGPIPE, SIG_IGN);
    setenv("TZ", "UTC", 1);

    // 创建 LogBuffer (默认 SerializedLogBuffer)
    LogBuffer* log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);

    // LogReader: 监听 /dev/socket/logdr, 把日志发给 logcat 等读者
    LogReader* reader = new LogReader(log_buffer, &reader_list);
    reader->startListener();

    // LogListener: 监听 /dev/socket/logdw, 接收来自应用的日志
    LogListener* swl = new LogListener(log_buffer);
    swl->StartListener();

    // CommandListener: 监听 /dev/socket/logd, 处理管理命令
    CommandListener* cl = new CommandListener(log_buffer, ...);
    cl->startListener();

    TEMP_FAILURE_RETRY(pause());  // 主线程挂起, 工作线程各自运行
}

logd 启动后创建三个 listener, 分别监听三个 socket:

Socket类型用途
/dev/socket/logdwSOCK_DGRAM接收日志写入
/dev/socket/logdrSOCK_SEQPACKET日志读取 (logcat)
/dev/socket/logdSOCK_STREAM管理命令 (clear, getLogSize 等)

4.2 接收日志: LogListener

LogListener.cpp7 在独立线程中循环接收日志:

void LogListener::HandleData() {
    // #define LOGGER_ENTRY_MAX_PAYLOAD 4068
    char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
    struct iovec iov = {buffer, sizeof(buffer) - 1};

    // POSIX CMSG 宏 (bionic sys/socket.h):
    // #define CMSG_ALIGN(len)       (((len)+sizeof(long)-1) & ~(sizeof(long)-1))  -- 按 long 对齐
    // #define CMSG_SPACE(len)       (CMSG_ALIGN(sizeof(cmsghdr)) + CMSG_ALIGN(len))  -- header + payload 的总空间
    // #define CMSG_FIRSTHDR(msg)    (msg->msg_controllen >= sizeof(cmsghdr) ? msg->msg_control : NULL)  -- 第一条辅助消息
    // #define CMSG_DATA(cmsg)       ((unsigned char*)(cmsg) + CMSG_ALIGN(sizeof(cmsghdr)))  -- 跳过 header 取 payload
    // #define CMSG_NXTHDR(mhdr,cm)  __cmsg_nxthdr(mhdr, cm)  -- 下一条辅助消息
    alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = { nullptr, 0, &iov, 1, control, sizeof(control), 0 };

    ssize_t n = recvmsg(socket_, &hdr, 0);

    // 提取凭据: 遍历辅助数据, 找到 SCM_CREDENTIALS 类型的 ucred
    struct ucred* cred = nullptr;
    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
    while (cmsg != nullptr) {
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) {
            cred = (struct ucred*)CMSG_DATA(cmsg);
            break;
        }
        cmsg = CMSG_NXTHDR(&hdr, cmsg);
    }

    // #define AID_LOGD 1036 -- log daemon
    if (cred->uid == AID_LOGD) return;  // 忽略 logd 自身日志, 防止递归

    // 解析 header, 写入 LogBuffer
    android_log_header_t* header = reinterpret_cast<android_log_header_t*>(buffer);
    log_id_t logId = static_cast<log_id_t>(header->id);

    char* msg = buffer + sizeof(android_log_header_t);
    n -= sizeof(android_log_header_t);

    logbuf_->Log(logId, header->realtime, cred->uid, cred->pid,
                 header->tid, msg, (uint16_t)n);
}

关键机制:

  • 使用 recvmsg() + SCM_CREDENTIALS 获取发送者的 uid/pid — 这是内核级别的身份验证, 应用无法伪造
  • android_log_header_t 包含 log_id, tid, realtime 时间戳
  • Security 日志会额外检查写入权限 (clientCanWriteSecurityLog)

4.3 存储: SerializedLogBuffer

SerializedLogBuffer.cpp8 是默认的日志存储实现:

int SerializedLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid,
                             pid_t pid, pid_t tid, const char* msg, uint16_t len) {
    if (!ShouldLog(log_id, msg, len)) {
        stats_->AddTotal(log_id, len);
        return -EACCES;
    }

    auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);

    auto lock = std::lock_guard{logd_lock};
    auto entry = LogToLogBuffer(logs_[log_id], max_size_[log_id], sequence,
                                realtime, uid, pid, tid, msg, len);
    stats_->Add(entry->ToLogStatisticsElement(log_id));

    MaybePrune(log_id);                    // 超出容量则裁剪旧日志
    reader_list_->NotifyNewLog(1 << log_id); // 通知等待中的读者
    return len;
}

存储结构:

  • 每个 log_id 有独立的 std::list<SerializedLogChunk> 链表
  • 每个 Chunk 是一块连续内存, 内部存储多条 SerializedLogEntry
  • 日志条目有全局递增的 sequence 编号, 用于读者定位
  • MaybePrune() 在 buffer 超出 max_size 时按 FIFO 裁剪旧 Chunk
  • NotifyNewLog() 唤醒所有关注该 log_id 的 LogReaderThread

Layer 5: logd -> logcat — 日志读取

5.1 logd 端: LogReader

LogReader.cpp9 监听 /dev/socket/logdr (SOCK_SEQPACKET):

bool LogReader::onDataAvailable(SocketClient* cli) {
    char buffer[255];
    int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
    buffer[len] = '\0';

    // 解析命令字符串, 例如: "stream lids=0,3,4 tail=100 pid=1234"
    unsigned long tail = 0;
    // ... 解析 tail, start, timeout, logMask, pid, nonBlock ...

    bool nonBlock = !fastcmp<strncmp>(buffer, "dumpAndClose", 12);

    // 创建读者线程
    auto entry = std::make_unique<LogReaderThread>(
        log_buffer_, reader_list_, std::move(socket_log_writer),
        nonBlock, tail, logMask, pid, start, sequence, deadline);

    reader_list_->AddAndRunThread(std::move(entry));
    return true;
}

每个连接的 logcat 客户端都会获得一个 LogReaderThread, 该线程:

  1. LogBufferFlushTo() 匹配的日志条目
  2. 通过 SocketLogWriter::Write() 发送给客户端
  3. 发完历史日志后, 阻塞等待 NotifyNewLog() 唤醒

SocketLogWriter::Write() 的数据格式:

bool Write(const logger_entry& entry, const char* msg) override {
    struct iovec iovec[2];
    iovec[0].iov_base = const_cast<logger_entry*>(&entry);
    iovec[0].iov_len = entry.hdr_size;       // logger_entry header
    iovec[1].iov_base = const_cast<char*>(msg);
    iovec[1].iov_len = entry.len;            // payload
    return client_->sendDatav(iovec, 1 + (entry.len != 0)) == 0;
}

5.2 logcat 端: liblog reader

logcat 通过 liblog 的 logd_reader.cpp10 读取日志:

// 连接 /dev/socket/logdr, 发送命令
static int logdOpen(struct logger_list* logger_list) {
    int sock = socket_local_client("logdr", SOCK_SEQPACKET, set_timeout);
    // 构造命令: "stream lids=0,3,4 tail=100"
    // 或 nonBlock 模式: "dumpAndClose lids=0,3,4"
    ret = TEMP_FAILURE_RETRY(write(sock, buffer, cp - buffer));
    return sock;
}

// 读取一条日志
int LogdRead(struct logger_list* logger_list, struct log_msg* log_msg) {
    int ret = logdOpen(logger_list);
    // SOCK_SEQPACKET 保证每次 recv 恰好收到一条完整日志
    ret = TEMP_FAILURE_RETRY(recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0));
    return ret;
}

SOCK_SEQPACKET 是一种可靠的、保持消息边界的 socket 类型, 非常适合这种 “一次传一条记录” 的场景.

Layer 6: logcat — 格式化输出

logcat.cpp11:

int main(int argc, char** argv) {
    Logcat logcat;
    return logcat.Run(argc, argv);
}

Logcat::Run() 的核心流程:

  1. 解析命令行参数 (-v, -s, -b, --pid 等)
  2. 调用 android_logger_list_alloc() + android_logger_open() 配置要监听的 buffer
  3. 循环调用 android_logger_list_read() 读取日志
  4. 格式化为用户看到的文本输出

默认监听的 buffer:

if (id_mask == 0) {
    id_mask = (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) |
              (1 << LOG_ID_CRASH) | (1 << LOG_ID_KERNEL);
}

这就是为什么 logcat 默认显示 main + system + crash + kernel 的日志, 而 events 和 radio 需要 -b events / -b radio 显式指定.

完整数据流总结

把上面的分析串起来, 一条 Log.d(TAG, "hello") 的完整时序:

sequenceDiagram
    participant App as App (liblog)
    participant logd as logd
    participant logcat as logcat

    App->>App: Log.d() → JNI → __android_log_buf_write()
    Note over App: isLoggable 检查
组装 iovec [priority|tag|msg] App->>logd: writev() /dev/socket/logdw App-->>App: PmsgWrite() → /dev/pmsg0 Note over logd: LogListener.HandleData()
recvmsg() + SCM_CREDENTIALS logd->>logd: SerializedLogBuffer.Log() Note over logd: 分配 sequence → 写入 Chunk → MaybePrune() logd->>logcat: LogReader → /dev/socket/logdr Note over logcat: recv() 一条完整日志
格式化输出到终端

附: 关键设计决策

  1. 为什么用 userspace 的 logd 而不是内核驱动? 早期 Android (4.x 之前) 确实用的是内核驱动 /dev/log/main 等. 后来迁移到 userspace logd 的原因:

    • 内核驱动的 buffer 管理不够灵活
    • 日志过滤、统计、裁剪策略在 userspace 更容易实现
    • 减少内核代码量, 降低安全攻击面
  2. 为什么写入用 SOCK_DGRAM, 读取用 SOCK_SEQPACKET?

    • 写入端 (DGRAM): 无连接, 低开销, 写入失败直接丢弃不阻塞应用 — 日志不应该影响应用性能
    • 读取端 (SEQPACKET): 面向连接, 保持消息边界, 保证每条日志完整接收 — logcat 需要可靠读取
  3. SCM_CREDENTIALS 的安全意义 logd 通过 recvmsg()SCM_CREDENTIALS 获取写入者的 uid / pid, 这是由内核填充的, 应用无法伪造. 这保证了:

    • 日志条目中的 uid / pid 信息是可信的
    • Security 日志的写入权限检查有效
    • logcat --uid 过滤基于真实身份

源码参考

AOSP 分支: main (platform/superproject/main) | 查阅工具: cs.android.com
适用版本: Android 15+ (logging 子系统自 Android 11 引入 logd userspace 重构后, 整体架构稳定)

Footnotes

  1. frameworks/base/core/java/android/util/Log.java

  2. frameworks/base/core/jni/android_util_Log.cpp

  3. system/logging/liblog/logger_write.cpp

  4. system/logging/liblog/logd_writer.cpp

  5. system/logging/liblog/pmsg_writer.cpp

  6. system/logging/logd/main.cpp

  7. system/logging/logd/LogListener.cpp

  8. system/logging/logd/SerializedLogBuffer.cpp

  9. system/logging/logd/LogReader.cpp

  10. system/logging/liblog/logd_reader.cpp

  11. system/logging/logcat/logcat.cpp

NOTE

文章作者: Catluo
文章链接: Android Log 全链路: 从 Log.d() 到 logcat 输出
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 授权协议。
转载请注明来源!