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 能够高效收集和管理系统运行日志,为开发人员排查问题提供了关键的 "信件记录"。

相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡3 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi003 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil5 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你5 小时前
Android View的绘制原理详解
android
移动开发者1号8 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号8 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best13 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk13 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin