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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui