Android 日志邮局:logd 的信件处理全流程

一、日志邮局的运作机制

在 Android 城市中,有一个专门处理日志信件的 "日志邮局(logd)",负责收集、分类和存储系统各组件的日志消息。当应用调用Log.i("Tag", "Hello")时,就像给邮局寄出了一封信,而 logd 则负责处理这些信件的整个流程。

1.1 写信与寄信:Java 层日志调用

java

arduino 复制代码
// Java层的"写信"过程
public class LogPostOffice {
    // 写一封普通信件(INFO级别)
    public static void sendInfoLetter(String tag, String message) {
        // 调用native方法"寄信"
        println_native(LOG_ID_MAIN, INFO, tag, message);
        // 这相当于把信交给邮局的快递员
    }
    
    // native方法由C++实现的"快递员"处理
    private native static int println_native(int bufID, int priority, String tag, String msg);
}

1.2 快递员的投递:native 层日志传递

cpp

运行

ini 复制代码
// C++快递员的工作流程(android_util_Log.cpp)
jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj) {
    // 提取信件内容
    const char* tag = env->GetStringUTFChars(tagObj, NULL);
    const char* msg = env->GetStringUTFChars(msgObj, NULL);
    
    // 特殊标签的信件需要投递到"无线电邮箱"
    if (是无线电相关标签(tag)) {
        bufID = LOG_ID_RADIO;
        tag = "use-Rlog/RLOG-" + tag; // 重命名标签
    }
    
    // 封装信件并投递
    struct iovec vec[3];
    vec[0].iov_base = (unsigned char*)&priority; // 信件优先级
    vec[1].iov_base = (void*)tag; // 标签
    vec[2].iov_base = (void*)msg; // 内容
    __android_log_buf_write(bufID, priority, tag, msg);
    
    // 归还信件内容
    env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);
    return 0;
}

1.3 邮局的专用通道:socket 通信

cpp

运行

scss 复制代码
// 日志邮局的专用快递通道(logd_write.c)
static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
    // 首次使用时初始化通道
    if (logd_fd < 0) {
        int socket_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        struct sockaddr_un un;
        memset(&un, 0, sizeof(un));
        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, "/dev/socket/logdw"); // 邮局地址
        
        // 连接到邮局
        connect(socket_fd, (struct sockaddr*)&un, sizeof(un));
        logd_fd = socket_fd; // 保存通道文件描述符
    }
    
    // 通过通道投递信件
    return __write_to_log_daemon(log_id, vec, nr);
}

// 正式投递信件到邮局
static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
    // 封装信件头部信息(发件人ID、时间等)
    android_log_header_t header;
    header.tid = gettid(); // 发件人线程ID
    clock_gettime(CLOCK_REALTIME, &header.realtime); // 发送时间
    
    // 写入pstore备用邮箱(紧急情况保存)
    if (pstore_fd >= 0) {
        writev(pstore_fd, newVec, i);
    }
    
    // 通过socket发送到邮局
    return writev(logd_fd, newVec + 1, i - 1);
}

二、日志邮局的内部运作

2.1 邮局的开业准备:logd 守护进程启动

cpp

运行

scss 复制代码
// 日志邮局开业流程(main.cpp)
int main(int argc, char* argv[]) {
    // 打开内核日志信箱
    int fdPmesg = open("/proc/kmsg", O_RDONLY | O_NDELAY);
    fdDmesg = open("/dev/kmsg", O_WRONLY);
    
    // 创建邮局工作线程
    logBuf = new LogBuffer(times); // 邮箱管理
    LogReader* reader = new LogReader(logBuf); // 信件查询服务
    LogListener* swl = new LogListener(logBuf, reader); // 信件接收服务
    CommandListener* cl = new CommandListener(logBuf, reader, swl); // 管理命令处理
    
    // 启动各服务线程
    reader->startListener(); // "信件查询窗口"线程
    swl->startListener(300); // "信件接收"线程
    cl->startListener(); // "管理命令"线程
    
    // 持续运行邮局
    TEMP_FAILURE_RETRY(pause());
    return 0;
}

2.2 邮箱管理:日志缓冲区机制

cpp

运行

scss 复制代码
// 邮箱初始化与容量管理(LogBuffer.cpp)
void LogBuffer::init() {
    // 邮箱容量优先级:
    // 1. 特定类型邮箱的配置(如persist.logd.size.system)
    // 2. 全局邮箱配置(persist.logd.size)
    // 3. 默认容量(256KB)
    unsigned long default_size = property_get_size("persist.logd.size");
    if (!default_size) default_size = property_get_size("ro.logd.size");
    
    // 为每个类型的邮箱设置容量
    log_id_for_each(i) {
        char key[PROP_NAME_MAX];
        snprintf(key, sizeof(key), "persist.logd.size.%s", android_log_id_to_name(i));
        unsigned long size = property_get_size(key);
        
        if (!size) {
            snprintf(key, sizeof(key), "ro.logd.size.%s", android_log_id_to_name(i));
            size = property_get_size(key);
        }
        
        if (!size) size = default_size;
        if (!size) size = LOG_BUFFER_SIZE; // 256KB
        if (setSize(i, size)) size = LOG_BUFFER_MIN_SIZE; // 64KB
    }
}

2.3 信件整理:日志裁剪策略

cpp

运行

arduino 复制代码
// 邮箱容量不足时的信件整理(LogBuffer.cpp)
void LogBuffer::maybePrune(log_id_t id) {
    size_t sizes = stats.sizes(id); // 当前邮箱已用容量
    unsigned long maxSize = log_buffer_size(id); // 邮箱最大容量
    
    // 当使用超过90%容量时开始整理
    if (sizes > maxSize) {
        size_t sizeOver = sizes - (maxSize * 9) / 10; // 超出部分
        size_t elements = stats.realElements(id); // 信件数量
        
        // 计算需要整理的信件数量
        size_t minElements = elements / 100; // 至少整理1%的信件
        if (minElements < 4) minElements = 4; // 最少整理4封
        unsigned long pruneRows = elements * sizeOver / sizes;
        if (pruneRows < minElements) pruneRows = minElements;
        if (pruneRows > 256) pruneRows = 256; // 最多整理256封
        
        // 执行整理
        prune(id, pruneRows);
    }
}

// 具体的信件整理过程
bool LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
    // 优先整理"黑名单"信件和发送最多的发件人信件
    if (是黑名单信件(id)) {
        移除黑名单信件(pruneRows);
    } else {
        // 找出发送最多的发件人
        uid_t worstUid = 找出最多产发件人(id);
        移除该发件人信件(pruneRows, worstUid);
    }
    return true;
}

三、用户如何查询信件:logcat 命令

bash

bash 复制代码
# logcat命令相当于邮局的查询窗口
logcat -b events  # 查询"事件邮箱"的信件
logcat -s "ActivityManager"  # 查找特定标签的信件
logcat -L  # 查看上次重启后的所有信件
logcat -f /sdcard/log.txt  # 保存信件到指定文件
logcat -g  # 查看邮箱容量
logcat -S  # 查看信件统计信息

cpp

运行

arduino 复制代码
// logcat的查询实现(logcat.cpp)
int main(int argc, char** argv) {
    // 解析查询参数
    int buffer = LOG_ID_MAIN;
    bool system = false, crash = false;
    while ((c = getopt(argc, argv, "b:f:g:s:LSD")) != -1) {
        switch (c) {
            case 'b':
                if (!strcmp(optarg, "main")) buffer = LOG_ID_MAIN;
                else if (!strcmp(optarg, "system")) buffer = LOG_ID_SYSTEM;
                else if (!strcmp(optarg, "crash")) buffer = LOG_ID_CRASH;
                else if (!strcmp(optarg, "events")) buffer = LOG_ID_EVENTS;
                else if (!strcmp(optarg, "radio")) buffer = LOG_ID_RADIO;
                break;
            // 其他参数处理...
        }
    }
    
    // 连接到邮局查询窗口
    int sock = socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET);
    // 发送查询请求并接收信件
    接收信件(sock, buffer);
    return 0;
}

四、邮局的关键参数配置

参数名称 类型 默认值 含义描述
persist.logd.size.system number 256K "系统邮箱" 的最大容量
persist.logd.size.radio number 256K "无线电邮箱" 的最大容量
logd.statistics bool false 启用信件统计功能
logd.auditd bool true 启用安全审核信件处理
persist.logd.size number 256K 所有邮箱的默认最大容量

bash

bash 复制代码
# 调整邮箱容量示例
setprop persist.logd.size.system 2m  # 将系统邮箱容量设为2MB
setprop logd.statistics true       # 启用统计功能

五、日志邮局的工作总结

  1. 信件投递流程

    • Java 层调用Log.i生成信件
    • native 层封装信件并通过 socket 投递到 logd
    • logd 接收信件并存储到对应邮箱
  2. 邮箱管理策略

    • 不同类型邮箱有独立容量配置
    • 容量不足时自动整理旧信件
    • 优先整理黑名单和高产发件人的信件
  3. 用户查询方式

    • 通过 logcat 命令按类型、标签查询信件

    • 支持保存信件到文件或查看统计信息

通过这套完整的 "日志邮局" 系统,Android 能够高效收集和管理系统运行日志,为开发人员排查问题提供了关键的 "信件记录"。

相关推荐
zepcjsj08012 小时前
简单实现支付密码的页面及输入效果
android
小阳睡不醒3 小时前
小白成长之路-部署Zabbix7(二)
android·运维
mmoyula4 小时前
【RK3568 PWM 子系统(SG90)驱动开发详解】
android·linux·驱动开发
你过来啊你6 小时前
Android用户鉴权实现方案深度分析
android·鉴权
kerli9 小时前
Android 嵌套滑动设计思想
android·客户端
恣艺10 小时前
LeetCode 854:相似度为 K 的字符串
android·算法·leetcode
阿华的代码王国10 小时前
【Android】相对布局应用-登录界面
android·xml·java
用户2070386194911 小时前
StateFlow与SharedFlow如何取舍?
android