一、日志邮局的运作机制
在 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 # 启用统计功能
五、日志邮局的工作总结
-
信件投递流程:
- Java 层调用
Log.i
生成信件 - native 层封装信件并通过 socket 投递到 logd
- logd 接收信件并存储到对应邮箱
- Java 层调用
-
邮箱管理策略:
- 不同类型邮箱有独立容量配置
- 容量不足时自动整理旧信件
- 优先整理黑名单和高产发件人的信件
-
用户查询方式:
-
通过 logcat 命令按类型、标签查询信件
-
支持保存信件到文件或查看统计信息
-
通过这套完整的 "日志邮局" 系统,Android 能够高效收集和管理系统运行日志,为开发人员排查问题提供了关键的 "信件记录"。