Android Logger日志系统

目录

  • 目录
  • 前言
  • 运行时库层日志库liblog
    • 源码分析
  • CC日志写入接口
  • Java日志写入接口
  • logcat工具分析
    • 基础数据结构

前言

该篇文章是我的读书和实践笔记。参考的是《Android系统源代码情景分析》。


运行时库层日志库——liblog

Android系统在运行时库层提供了一个用来和Logger日志驱动程序进行交互的日志库liblog。通过日志库liblog提供的接口,应用程序就可以方便地往Logger日志驱动程序中写入日志记录。

位于运行时库层的C/C++日志写入接口和位于应用程序框架层的Java日志写入接口都是通过liblog库提供的日志写入接口来往Logger日志驱动程序中写入日志记录的。


源码分析

日志库liblog提供的日志记录写入接口实现在logd_write.c文件中,它的源码位置为:/system/core/liblog/logd_write.c。

根据写入的日志记录的类型不同,这些函数可以划分为三个类别,其中:

  1. 函数__android_log_assert、__android_log_vprint和__android_log_print用来写入类型为main的日志记录。
  2. 函数__android_log_btwrite和__android_log_bwrite用来写入类型为events的日志记录。
  3. 函数__android_log_buf_print可以写入任意一种类型的日志记录。

无论写入的是什么类型的日志记录,它们最终都是通过调用函数write_to_log写入到Logger日志驱动程序中的。write_to_log是一个函数指针,它开始时指向函数__write_to_log_init。因此,当函数write_to_log第一次被调用时,实际上执行的是函数__write_to_log_init。函数__write_to_log_init主要是进行一些日志库初始化操作,接着函数指针write_to_log重定向到函数__write_to_log_kernel或者__write_to_log_null中,这取决于能否成功地将日志设备文件打开。

源码分析如上,源码实现如下:

// 先声明,后引用
static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;

// 一些定义在system/core/include/cutils/log.h中的宏
typedef enum {
    LOG_ID_MAIN = 0,
    LOG_ID_RADIO = 1,
    LOG_ID_EVENTS = 2,
    LOG_ID_SYSTEM = 3,

    LOG_ID_MAX
} log_id_t;

#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_RADIO "log/radio"
#define LOGGER_LOG_EVENTS "log/events"
#define LOGGER_LOG_SYSTEM "log/system"

// 真正函数执行的地方
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
    if (write_to_log == __write_to_log_init) {
        log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
        log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
        log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
        log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);

        // 修改write_to_log函数指针
        write_to_log = __write_to_log_kernel;

        if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 || log_fds[LOG_ID_EVENTS] < 0) {
            log_close(log_fds[LOG_ID_MAIN]);
            log_close(log_fds[LOG_ID_RADIO]);
            log_close(log_fds[LOG_ID_EVENTS]);
            log_fds[LOG_ID_MAIN] = -1;
            log_fds[LOG_ID_RADIO] = -1;
            log_fds[LOG_ID_EVENTS] = -1;
            write_to_log = __write_to_log_null;
        }

        if (log_fds[LOG_ID_SYSTEM] < 0) {
            log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
        }
    }

    return write_to_log(log_id, vec, nr);
}

通过上述代码,我们在替换宏定义之后,是可以知道调用log_open打开的分别是/dev/log/main、/dev/log/radio、/dev/log/events、/dev/log/system四个日志设备文件。而宏log_open定义在system/core/liblog/logd_write.c中:

#if FAKE_LOG_DEVICE
// 不需要care这里,真正编译的时候FAKE_LOG_DEVICE为0
#else
#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC)
#define log_writev(filedes, vector, count) writev(filedes, vector, count)
#define log_close(filedes) close(filedes)
#endif

从上面代码可以看出,log_open的真正实现是open函数。

回到最开始的地方,如果log_open的文件都是ok的,那接下来会调用__write_to_log_kernel函数,源码实现如下:

static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
    ssize_t ret;
    int log_fd;

    if ((int)log_id < (int)LOG_ID_MAX) {
        log_fd = log_fds[(int)log_id];
    } else {
        return EBADF;
    }

    do {
        ret = log_writev(log_fd, vec, nr);
    } while (ret < 0 && errno == EINTR);

    return ret;
}

函数__write_to_log_kernel会根据参数log_id在全局数组log_fds中找到对应的日志设备文件描述符,然后调用宏log_writev,即函数writev,把日志记录写入到Logger日志驱动程序中。

如果设备文件打开失败的话,write_to_log函数指针会被赋值为__write_to_log_kernel,这个函数其实什么都没有做,只是返回了个-1。所以就不贴源码了。

最后,我们在分析一下__android_log_buf_write函数。因为C/C++日志写入接口和Java日志写入接口最终都是调用了这个函数完成了日志的写入。源码如下:

int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
    struct iovec vec[3];
    char tmp_tag[32];

    if (! tag) tag = "";

    if ((bufID != LOG_ID_RADIO) &&
        (!strcmp(tag, "HTC_RIL") ||
        (!strncmp(tag, "RIL", 3)) ||
        (!strncmp(tag, "IMS", 3)) ||
        !strcmp(tag, "AT") ||
        !strcmp(tag, "GSM") ||
        !strcmp(tag, "STK") ||
        !strcmp(tag, "CDMA") ||
        !strcmp(tag, "PHONE") ||
        !strcmp(tag, "SMS"))) {
            bufID = LOG_ID_RADIO;
            snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
            tag  = tmp_tag;
    }

    vec[0].iov_base = (unsigned char *) &prio;
    vec[0].iov_len = 1;
    vec[1].iov_base = (void *) tag;
    vec[1].iov_len = strlen(tag) + 1;
    vec[2].iov_base = (void *) msg;
    vec[2].iov_len = strlen(msg) + 1;

    return write_to_log(log_id, vec, 3);
}

在默认情况下,函数__android_log_write写入的日志记录类型为main。然后,如果传进来的日志记录的标请以”RIL”等标志开头,那么它就会被认为是类型是radio的日志记录。


C/C++日志写入接口

Android系统提供了三组常用的C/C++宏来封装日志写入接口。之所以这样做,是为了方便开发同学进行日志的开关控制,例如不在发布版本中打开日志。

三组宏定义分别为:

  1. ALOGV,ALOGD,ALOGI,ALOGW和ALOGE。用来记录类型为main的日志。
  2. SLOGV,SLOGD,SLOGI,SLOGW和SLOGE,用来写入类型为system的日志。
  3. LOG_EVENT_INT,LOG_EVENT_LONG和LOG_EVENT_STRING,它们用来写入类型为events的日志记录。

这些宏定义在system/core/include/log/log.h中,并且使用了一个LOG_NDEBUG的宏来作为日志开关。

具体源码如下:

// 日志开关
#ifndef LOG_NDEBUG
#ifdef NDEBUG
#define LOG_NDEBUG 1
#else
#define LOG_NDEBUG 0
#endif
#endif

// 以ALOGE为例子
#ifnded ALOGE
#define ALOGE(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif

#ifndef ALOG
#define ALOG(priority, tag, ...) \
    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif

#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...) \
    android_printLog(priority, tag, __VA_ARGS__)
#endif

# 回到了我们熟悉的__android_log_print函数
#define android_printLog(prio, tag, fmt...)\
    __android_log_print(prio, tag, fmt)

Java日志写入接口

Android系统在应用程序框架中定义了三个Java日志写入接口,它们分别是android.util.Log、android.util.Slog和android.util.EventLog,写入的日志记录类型分别为main、system和events。

这里主要分析android.util.log的实现。源码如下:

public final class Log {

    /**
     * Priority constant for the println method; use Log.v.
     */
    public static final int VERBOSE = 2;

    /**
     * Priority constant for the println method; use Log.d.
     */
    public static final int DEBUG = 3;

    /**
     * Priority constant for the println method; use Log.i.
     */
    public static final int INFO = 4;

    /**
     * Priority constant for the println method; use Log.w.
     */
    public static final int WARN = 5;

    /**
     * Priority constant for the println method; use Log.e.
     */
    public static final int ERROR = 6;

    /**
     * Priority constant for the println method.
     */
    public static final int ASSERT = 7;

    private Log() {
    }

    /**
     * Send a {@link #VERBOSE} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int v(String tag, String msg) {
        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
    }

    /**
     * Send a {@link #DEBUG} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int d(String tag, String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

    /**
     * Send an {@link #INFO} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int i(String tag, String msg) {
        return println_native(LOG_ID_MAIN, INFO, tag, msg);
    }

    /**
     * Send a {@link #WARN} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int w(String tag, String msg) {
        return println_native(LOG_ID_MAIN, WARN, tag, msg);
    }

    /**
     * Send an {@link #ERROR} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int e(String tag, String msg) {
        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    }

    /** @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 native int println_native(int bufID,
            int priority, String tag, String msg);
}

可以看到,JAVA应用层logger代码是调用了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;

    if (msgObj == NULL) {
        jniThrowNullPointerException(env, "println needs a message");
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jniThrowNullPointerException(env, "bad bufID");
        return -1;
    }

    if (tagObj != NULL) {
        tag = env->GetStringUTFChars(tagObj, NULL);
    }
    msg = env->GetStringUTFChars(msgObj, NULL);
    int res = -1;
    // 真正日志写入的函数(liblog.so中的函数)
    res = __android_log_buf_write(bufID, (android_LogPriority), tag, msg);
    return res;
}

logcat工具分析

前面分析的将日志记录写入到Logger日志中的目的就是通过logcat工具将它们读出来,然后给开发人员进行分析。

Logcat的用法很多,但是这里主要从源码的角度出发,分析Logcat的四个部分:

  1. 基础数据结构。
  2. 初始化过程。
  3. 日志记录的读取过程。
  4. 日志记录的输出过程。

logcat的源码位于:system/core/logcat.cpp中。


基础数据结构

首先是定义在system/core/include/log/logger.h中的logger_entry,定义如下:

struct logger_entry {
    uint16_t len;
    uint16_t __pad;
    int32_t pid;
    int32_t tid;
    int32_t sec;
    int32_t nsec;
    char msg[0];
};

结构体logger_entry用来描述一条日志记录。其中,char msg[0]指针用来记录消息实体内容。

然后,在看一下queued_entry_t结构体,源码如下:

struct queued_entry_t {

};

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 17:24:16

Android Logger日志系统的相关文章

入门篇:11.Android中日志系统和权限系统

一.安卓中的日志系统 1.java中常用的两个日志 System.out.println();//普通日志 System.err.println();//警告日志 2.android中常用的日志种类 Log.e(Tag,"错误信息"); Log.w(Tag,"警告信息"); Log.i(Tag,"普通信息"); Log.d(Tag,"调试信息"); Log.v(Tag,"无用信息"); ps:这个log.v

android logger 日志工具

https://github.com/orhanobut/logger 基础使用:https://blog.csdn.net/github_33304260/article/details/54799169 原文地址:https://www.cnblogs.com/zquan/p/9498825.html

Android应用程序框架层和系统运行库层日志系统源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6598703 在开发Android应用程序时,少不了使用Log来监控和调试程序的执行.在上一篇文章Android日志系统驱动程序Logger源代码分析中,我们分析了驱动程序Logger的源代码,在前面的文章浅谈Android系统开发中Log的使用一文,我们也简单介绍在应用程序中使Log的方法,在这篇文章中,我们将详细介绍Android应用程序框架

Android日志系统Logcat源代码简要分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6606957 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Android应用程序框架层和系统运行库层日志系统源代码中,介绍了Android内核空间层.系统运行库层和应用程序框架层日志系统相关的源代码,其中,后一篇文章着重介绍了日志的写入操作.为了描述完整性,这篇文章着重介绍日志的读取操作,这就是我们在开发Android应用

Android日志系统驱动程序Logger源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6595744 我们知道,在Android系统中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,而在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,取决于你编写的是Android应用程序还是系统组件.在前面的文章浅谈Android系统开发中LOG的使用中,已经简要地介绍了在Android应用程序开发中

Java日志系统---Logger之简单入门

Java 中自带的日志系统,今天抽空了解了一点,算是入了门,所以将自己的一些心得记录下来,以备日后查看,有兴趣的朋友,看到此文章,觉得有错误或需要添加的地方,请在下方评论留言,大家可以共同进步,谢谢:) Java中关于日志系统的API,在 java.util.logging 包中,在这个包中,Logger类很重要. Logger类是用来记录 某个级别的日志消息: 级别共分为以下几类,从上倒下,级别依次下降: SEVERE(严重)------级别最高 WARNING(警告) INFO CONFIG

走进JavaWeb技术世界9:Java日志系统的诞生与发展

> 微信公众号[黄小斜]大厂程序员,互联网行业新知,终身学习践行者.关注后回复「Java」.「Python」.「C++」.「大数据」.「机器学习」.「算法」.「AI」.「Android」.「前端」.「iOS」.「考研」.「BAT」.「校招」.「笔试」.「面试」.「面经」.「计算机基础」.「LeetCode」 等关键字可以获取对应的免费学习资料. ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9waWMzLnpoaW1nLmNvbS84MC92Mi1jND

日志系统之Flume采集加morphline解析

概述 这段时间花了部分时间在处理消息总线跟日志的对接上.这里分享一下在日志采集和日志解析中遇到的一些问题和处理方案. 日志采集-flume logstash VS flume 首先谈谈我们在日志采集器上的选型.由于我们选择采用ElasticSearch作为日志的存储与搜索引擎.而基于ELK(ElasticSearch,Logstash,Kibana)的技术栈在日志系统方向又是如此流行,所以把Logstash列入考察对象也是顺理成章,Logstash在几大主流的日志收集器里算是后起之秀,被Elas

日志系统之扩展Flume-LineDeserializer

本人博客文章如未特别注明皆为原创!如有转载请注明出处:http://blog.csdn.net/yanghua_kobe/article/details/46595401 继续闲聊日志系统,在之前的博文里已提到我们在日志收集上的选择是flume-ng.应用程序将日志打到各自的日志文件或指定的文件夹(日志文件按天滚动),然后利用flume的agent去日志文件中收集. Deserializer简介 flume将一条日志抽象成一个event.这里我们从日志文件中收集日志采用的是定制版的SpoolDi