系列目录:第一篇:全景图与架构概览 | 第二篇:logd守护进程—启动、初始化与Socket通信 | 第三篇:liblog库—日志写入的完整链路 | 第四篇:日志写入接口—Java层与Native层 | 第五篇:日志读取—logcat源码深度分析 | 第六篇:日志缓冲区管理—容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析
本篇面向开发者,讲清各种日志接口的源码实现与使用场景。所有接口最终都收敛到 __android_log_buf_write()。
一、写入接口全景图
Java层 Native层 (liblog)
──────────────────────────────────────────────────────────────
Log.d(TAG, msg) ────┐
Slog.d(TAG, msg) ────┤
EventLog.writeEvent()────┤ __android_log_buf_write()
│ │
ALOGD("msg") ────┘ ▼
SLOGD("msg") write_to_log()
RLOGD("msg") │
▼
logdWrite() → writev(/dev/socket/logdw)
pmsgWrite() → writev(/dev/pmsg0)
注意:图中的
write_to_log()是函数指针,初始指向__write_to_log_init,初始化后替换为__write_to_log_daemon(详见第三篇)。
二、android.util.Log — 应用层日志
源码路径:frameworks/base/core/java/android/util/Log.java
2.1 优先级常量与缓冲区 ID
public final class Log {
public static final int VERBOSE = 2;
public static final int DEBUG = 3;
public static final int INFO = 4;
public static final int WARN = 5;
public static final int ERROR = 6;
public static final int ASSERT = 7;
/** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
常量是
public的,但带有@hide注解,对 SDK 应用不可见。系统服务通过 framework.jar 可以直接引用。
2.2 核心 JNI 方法
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
public static native boolean isLoggable(String tag, int level);
private static native int logger_entry_max_payload_native();
println_native 和 isLoggable 都是 native 方法,没有 Java 方法体,实现位于 android_util_Log.cpp。logger_entry_max_payload_native 返回 LOGGER_ENTRY_MAX_PAYLOAD 常量值,用于 Java 层的长消息分块逻辑。
2.3 d/v/i/w/e 快捷方法(无异常参数)
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
public static int i(String tag, String msg) {
return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
public static int w(String tag, String msg) {
return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
public static int e(String tag, String msg) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
全部写入 LOG_ID_MAIN(0) 缓冲区,直接调用 println_native JNI 方法。
2.4 带异常参数的版本 — printlns()
当传入 Throwable 参数时,不走 println_native,而是调用另一个内部方法 printlns():
public static int d(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
}
public static int e(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
}
// v/i/w 同理,全部调用 printlns
printlns() 的实现比简单字符串拼接复杂得多:
public static int printlns(int bufID, int priority, String tag, String msg,
Throwable tr) {
ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
// 计算缓冲区大小:LOGGER_ENTRY_MAX_PAYLOAD - 两个终止符 - tag 长度 - 预留
int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD
- 2 - (tag != null ? tag.length() : 0) - 32;
bufferSize = Math.max(bufferSize, 100);
LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
lbbw.println(msg);
if (tr != null) {
// UnknownHostException 和 DeadSystemException 特殊处理,不打印堆栈
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) break;
if (t instanceof DeadSystemException) {
lbbw.println("DeadSystemException: ...");
break;
}
t = t.getCause();
}
if (t == null) tr.printStackTrace(lbbw);
}
lbbw.flush();
return logWriter.getWritten();
}
关键设计:LineBreakBufferedWriter 按换行符将长消息切分成多个 chunk,每个 chunk 通过 ImmediateLogWriter.write() 调用 println_native 写入。这样长堆栈不会因为超过 LOGGER_ENTRY_MAX_PAYLOAD 而被截断。
2.5 wtf (What a Terrible Failure)
public static int wtf(String tag, String msg) {
return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
}
static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
boolean system) {
TerribleFailure what = new TerribleFailure(msg, tr);
// ★ 注意:wtf 使用 ERROR 级别,不是 ASSERT
// 代码注释:"Only mark this as ERROR, do not use ASSERT since that should be
// reserved for cases where the system is guaranteed to abort."
int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
sWtfHandler.onTerribleFailure(tag, what, system);
return bytes;
}
重要:
wtf实际写入的日志级别是 ERROR,而不是ASSERT。ASSERT被保留给"系统保证会 abort"的场景。onTerribleFailure回调可能触发 DropBox 上报或进程终止,但不一定。
2.6 isLoggable — 运行时日志开关
public static native boolean isLoggable(String tag, int level);
这是一个 native 方法,JNI 实现在 android_util_Log.cpp 中:
static jboolean isLoggable(const char* tag, jint level) {
return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}
读取系统属性 log.tag.<TAGNAME>,与默认级别 ANDROID_LOG_INFO 比较。tag 长度超过 PROPERTY_KEY_MAX - sizeof("log.tag.") 时抛出 IllegalArgumentException。
三、android_util_Log.cpp — JNI 桥接层
源码路径:frameworks/base/core/jni/android_util_Log.cpp
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;
// 步骤0:参数校验
if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}
// 步骤1:从 Java 字符串获取 C 字符串
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
// 步骤2:调用 liblog 核心函数
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
// 步骤3:释放 C 字符串
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
// 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 },
};
JNI 层极简:Java 层所有重载(d/v/i/w/e + Throwable)在进入 JNI 之前就已展开为 tag+msg 字符串(printlns 方法负责分块),JNI 层只需透传参数到 __android_log_buf_write()。
注意:Java 层的
priority和 Native 层的android_LogPriority枚举值恰好一致(VERBOSE=2, DEBUG=3, ...),无需转换。
四、android.util.Slog — 系统框架日志
源码路径:frameworks/base/core/java/android/util/Slog.java
/** @hide */
public final class Slog {
private Slog() {}
public static int v(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
}
public static int i(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
}
public static int w(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
}
public static int e(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
}
}
Slog 与 Log 的唯一区别:bufID 参数不同。
Log.d()→LOG_ID_MAIN(0)→ logd 的main缓冲区Slog.d()→LOG_ID_SYSTEM(3)→ logd 的system缓冲区
差异点:
| 特性 | Log | Slog |
|---|---|---|
| 缓冲区 | main (0) | system (3) |
| 可见性 | public API | @hide,仅系统内部使用 |
| Throwable 处理 | printlns() 分块写入 | 直接拼接 msg + '\n' + getStackTraceString(tr) |
| wtf 行为 | 可能触发进程终止 | 始终异步处理,不终止进程(system=true) |
Slog 的 wtf 系列方法调用 Log.wtf(LOG_ID_SYSTEM, ..., true),最后一个参数 system=true 确保只做 DropBox 上报,不会终止 system_server 进程。
日志命名约定
Android 7 源码中系统服务的 TAG 通常使用服务名缩写:
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
static final String TAG = "ActivityManager";
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
static final String TAG = "WindowManager";
五、android.util.EventLog — 事件日志(二进制格式)
源码路径:frameworks/base/core/java/android/util/EventLog.java
5.1 writeEvent 是 native 方法
public class EventLog {
public static native int writeEvent(int tag, int value);
public static native int writeEvent(int tag, long value);
public static native int writeEvent(int tag, float value);
public static native int writeEvent(int tag, String str);
public static native int writeEvent(int tag, Object... list);
}
关键:所有
writeEvent重载都是 native 方法,二进制编码在android_util_EventLog.cpp的 JNI 层完成,不是在 Java 层编码。
5.2 JNI 层编码实现
源码路径:frameworks/base/core/jni/android_util_EventLog.cpp
编码流程(以 writeEvent(int tag, int value) 为例):
static jint android_util_EventLog_writeEvent_Integer(JNIEnv* env, jobject clazz,
jint tag, jint value)
{
// 直接调用 android_btWriteLog,传入 tag + 类型 + 值
return android_btWriteLog(tag, EVENT_TYPE_INT, &value, sizeof(value));
}
android_btWriteLog 最终调用 __android_log_btwrite,在该函数内部构建 iovec 并调用 write_to_log(LOG_ID_EVENTS, vec, nr)。
对于 Object... 可变参数版本,JNI 层遍历数组元素,根据运行时类型(Integer/Long/Float/String)逐个编码为二进制格式:
static jint android_util_EventLog_writeEvent_Array(JNIEnv* env, jobject clazz,
jint tag, jobjectArray value) {
uint8_t buf[MAX_EVENT_PAYLOAD];
// ... 遍历 value 数组,按类型编码到 buf 中 ...
buf[0] = EVENT_TYPE_LIST; // 类型标记:列表
buf[1] = copied; // 元素个数
buf[pos++] = '\n'; // 终止符
return android_bWriteLog(tag, buf, pos); // 写入 LOG_ID_EVENTS
}
5.3 EventLog 的特殊之处
- 写入
LOG_ID_EVENTS(2)缓冲区 - 日志内容为二进制编码(非纯文本字符串),格式为
[类型标记][数据]\n - 类型标记:
EVENT_TYPE_INT=0, EVENT_TYPE_LONG=1, EVENT_TYPE_STRING=2, EVENT_TYPE_LIST=3, EVENT_TYPE_FLOAT=4 /system/etc/event-log-tags文件定义 tag 编号与字段名称/类型的映射logcat -b events读取时会根据映射表解码为可读文本
5.4 event-log-tags 文件示例
文件路径:system/core/logcat/event.logtags
# system/core/logcat/event.logtags
42 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
43 am_proc_bound (User|1|5),(PID|1|5),(Process Name|3)
44 am_anr (User|1|5),(PID|1|5),(Package Name|3),(Flags|1|5)
...
格式:<tag编号> <tag名称> (<字段名称>|<类型>|<字节数>),...
5.5 EventLog 的读取能力
EventLog 还提供了 readEvents() native 方法用于读取事件日志:
public static native void readEvents(int[] tags, Collection<Event> output)
throws IOException;
以及 getTagName() / getTagCode() 方法,通过读取 /system/etc/event-log-tags 文件实现 tag 号与名称的双向映射。
六、Native 层日志宏 — ALOGD / SLOGD / RLOGD 系列
源码路径:system/core/include/log/log.h
6.1 LOG_TAG 与 LOG_NDEBUG
// 必须在 include <log/log.h> 前定义 LOG_TAG
#ifndef LOG_TAG
#define LOG_TAG NULL
#endif
// LOG_NDEBUG 控制 ALOGV 的编译开关
#ifndef LOG_NDEBUG
#ifdef NDEBUG
#define LOG_NDEBUG 1 // release 版本:ALOGV 编译为空
#else
#define LOG_NDEBUG 0 // debug 版本:ALOGV 正常输出
#endif
#endif
6.2 ALOGD 系列宏展开链
// 第一步:用户级宏
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
// ALOGV 特殊处理:LOG_NDEBUG=1 时编译为空
#if LOG_NDEBUG
#define ALOGV(...) do { if (0) { __ALOGV(__VA_ARGS__); } } while (0)
#else
#define ALOGV(...) __ALOGV(__VA_ARGS__)
#endif
// 第二步:基础宏 ALOG
#define ALOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
// 第三步:LOG_PRI
#define LOG_PRI(priority, tag, ...) \
android_printLog(priority, tag, __VA_ARGS__)
// 第四步:android_printLog
#define android_printLog(prio, tag, fmt...) \
__android_log_print(prio, tag, fmt)
6.3 SLOGD / RLOGD 系列
// 系统日志(写入 LOG_ID_SYSTEM)
#define SLOGD(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define SLOGI(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define SLOGW(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#define SLOGE(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
// 无线日志(写入 LOG_ID_RADIO)
#define RLOGD(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
// RLOGI/RLOGW/RLOGE 同理
SLOGD和RLOGD系列直接展开为__android_log_buf_print(),指定了bufID参数,跳过了android_printLog→__android_log_print的中间路程。
6.4 IF_ALOGV 条件宏
#if LOG_NDEBUG
#define IF_ALOGV() if (false)
#else
#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
#endif
#define IF_ALOG(priority, tag) \
if (android_testLog(ANDROID_##priority, tag))
IF_ALOGV 在 release 版本中直接为 if (false),编译器会优化掉整个代码块,避免开销。
七、__android_log_print() — 通往 liblog 的最后一公里
源码路径:system/core/liblog/logger_write.c
// __android_log_print 是真实的函数,不是宏
LIBLOG_ABI_PUBLIC int __android_log_print(int prio, const char *tag,
const char *fmt, ...)
{
va_list ap;
char buf[LOG_BUF_SIZE]; // LOG_BUF_SIZE = 1024
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_write(prio, tag, buf);
}
// __android_log_write 是 __android_log_buf_write 的 MAIN 缓冲区包装
LIBLOG_ABI_PUBLIC int __android_log_write(int prio, const char *tag,
const char *msg)
{
return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}
// __android_log_buf_print 是带 bufID 参数的版本
LIBLOG_ABI_PUBLIC int __android_log_buf_print(int bufID, int prio,
const char *tag,
const char *fmt, ...)
{
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_buf_write(bufID, prio, tag, buf);
}
完整的宏展开链路:
ALOGD("value=%d", x)
│
▼
ALOG(LOG_DEBUG, LOG_TAG, "value=%d", x)
│
▼
LOG_PRI(ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
│
▼
android_printLog(ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
│
▼
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
│ 内部:vsnprintf → 格式化字符串
│ 调用:__android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, "value=42")
▼
__android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_DEBUG, LOG_TAG, "value=42")
SLOGD 的链路更短:
SLOGD("value=%d", x)
│
▼
__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
│ 内部:vsnprintf → 格式化字符串
▼
__android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, "value=42")
八、各接口的缓冲区映射总表
| 接口 | bufID | 缓冲区 | 权限要求 |
|---|---|---|---|
Log.d/v/i/w/e/wtf() | 0 | main | 任何进程 |
ALOGD/V/I/W/E() | 0 | main | 任何进程 |
Slog.d/v/i/w/e() | 3 | system | 系统进程 (UID=1000) |
SLOGD/V/I/W/E() | 3 | system | 系统进程 |
EventLog.writeEvent() | 2 | events | 系统服务 |
RLOGD/V/I/W/E() | 1 | radio | rild 及无线相关进程 |
| (内部) | 4 | crash | 系统崩溃处理 |
| (内部) | 5 | security | SELinux 审计日志 |
普通应用只能写入 main 缓冲区,这是 Android 安全模型的一部分。尝试写 system/events/radio 会被 SELinux 或 UID 权限检查阻止。
说明:Android 7 的 AOSP 源码中不存在
android.util.Rlog这个 Java 类。无线通信相关代码通常直接使用Log.println_native(LOG_ID_RADIO, ...)或 Native 层的RLOGD宏。如果在某些厂商 ROM 中看到Rlog,那是厂商自行添加的封装类。
九、性能优化建议
使用 isLoggable() 前置判断
// ❌ 不好的写法 — 即使不输出,字符串拼接仍会执行
Log.d(TAG, "Processing item: " + heavyToString(obj));
// ✅ 好的写法 — 不满足条件时跳过字符串拼接
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Processing item: " + heavyToString(obj));
}
Native 层使用 IF_ALOGV
// ❌ ALOGV 内部的 vsnprintf 即使最终不输出也会执行
ALOGV("complex data: %s", expensive_format(data));
// ✅ 使用 IF_ALOGV 保护 — release 版编译为 if(false),整体被优化掉
IF_ALOGV() {
ALOGV("complex data: %s", expensive_format(data));
}
日志级别的选择策略
| 级别 | 何时使用 |
|---|---|
VERBOSE | 详细调试信息,不应出现在发布版中 |
DEBUG | 开发调试信息,user 版本可关闭 |
INFO | 值得记录的正常事件(如启动完成、连接成功) |
WARN | 异常但可恢复的情况 |
ERROR | 错误、失败的操作 |
ASSERT/wtf | "不可能发生"的情况,应视为 bug |
十、本篇总结
- Java 层接口(
Log/Slog/EventLog)的唯一区别是bufID参数 println_native是 JNI native 方法,Java 层不实现任何编码逻辑printlns()是 Java 层唯一做额外处理的方法——负责长堆栈的分块写入EventLog.writeEvent()全部是 native 方法,二进制编码在android_util_EventLog.cpp的 JNI 层完成wtf实际使用ERROR级别,不是ASSERT__android_log_print是真实函数(不是宏),内部做vsnprintf格式化后调用__android_log_writeALOGD展开为__android_log_print→__android_log_write→__android_log_buf_write(LOG_ID_MAIN, ...)SLOGD展开为__android_log_buf_print→__android_log_buf_write(LOG_ID_SYSTEM, ...)LOG_NDEBUG控制ALOGV的编译时开关- 所有调用最终汇聚到同一个函数:
__android_log_buf_write()
下一篇将分析 logcat 的源码,看它如何从 logd 读取、过滤和格式化日志。
555

被折叠的 条评论
为什么被折叠?



