Android 7系统日志(四)日志写入接口—Java层与Native层

系列目录第一篇:全景图与架构概览 | 第二篇: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_nativeisLoggable 都是 native 方法,没有 Java 方法体,实现位于 android_util_Log.cpplogger_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,而不是 ASSERTASSERT 被保留给"系统保证会 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 缓冲区

差异点

特性LogSlog
缓冲区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 同理

SLOGDRLOGD 系列直接展开为 __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()0main任何进程
ALOGD/V/I/W/E()0main任何进程
Slog.d/v/i/w/e()3system系统进程 (UID=1000)
SLOGD/V/I/W/E()3system系统进程
EventLog.writeEvent()2events系统服务
RLOGD/V/I/W/E()1radiorild 及无线相关进程
(内部)4crash系统崩溃处理
(内部)5securitySELinux 审计日志

普通应用只能写入 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_write
  • ALOGD 展开为 __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 读取、过滤和格式化日志。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值