Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Logger日志系統

Android Logger日志系統

編輯:關於Android編程

目錄

目錄 前言 運行時庫層日志庫liblog 源碼分析 CC日志寫入接口 Java日志寫入接口 logcat工具分析 基礎數據結構


運行時庫層日志庫——liblog

Android系統在運行時庫層提供了一個用來和Logger日志驅動程序進行交互的日志庫liblog。通過日志庫liblog提供的接口,應用程序就可以方便地往Logger日志驅動程序中寫入日志記錄。

位於運行時庫層的C/C++日志寫入接口和位於應用程序框架層的Java日志寫入接口都是通過liblog庫提供的日志寫入接口來往Logger日志驅動程序中寫入日志記錄的。


源碼分析

日志庫liblog提供的日志記錄寫入接口實現在logd_write.c文件中,它的源碼位置為:/system/core/liblog/logd_write.c。

根據寫入的日志記錄的類型不同,這些函數可以劃分為三個類別,其中:

函數__android_log_assert、__android_log_vprint和__android_log_print用來寫入類型為main的日志記錄。 函數__android_log_btwrite和__android_log_bwrite用來寫入類型為events的日志記錄。 函數__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++宏來封裝日志寫入接口。之所以這樣做,是為了方便開發同學進行日志的開關控制,例如不在發布版本中打開日志。

三組宏定義分別為:

ALOGV,ALOGD,ALOGI,ALOGW和ALOGE。用來記錄類型為main的日志。 SLOGV,SLOGD,SLOGI,SLOGW和SLOGE,用來寫入類型為system的日志。 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的四個部分:

基礎數據結構。 初始化過程。 日志記錄的讀取過程。 日志記錄的輸出過程。

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 {

};

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved