android系統中log機制


android系統中log機制

背景

習慣了Linux開發的我,轉到安卓以后,對於安卓開發的很多問題沒有經驗。看到同事解決問題都會看logcat,因此有必要了解一下這些東西。

介紹

Android提供了用戶級輕量的LOG機制,它的實現貫穿了Java,JNI,本地c/c++實現以及LINUX內核驅動等Android的各個層次,而且足夠簡單清晰,是一個相當不錯的解讀案例。

LOG的實現架構

原文(有刪改):https://blog.csdn.net/thl789/article/details/6629905

LOG的運行環境

下圖是Android官方網站上給出的Android的Debug環境。

image-20210227110957659

Android的LOG機制當然也在這個環境中運行。我們重點關注Emulator和Device上運行的部分,App VMs產生LOG信息,並與ADB Device Daemon交互輸出這些信息,而ADB Device Daemon又通過相應的協議通過USB(Device)或本地連接(Emulator),與PC上運行的ADB Host Daemon交互,通過PC上的調試工具呈現給用戶。JDWP Debugger、DDMS、ADB Host Daemon以及ADB Device Daemon之間的交互與其使用的協議,不在本文討論范圍之內。

本文討論的內容運行在Emulator/Device上,產生LOG信息,並通過程序LogCat輸出。

LOG的實現架構

Android中LOG的實現架構如下圖所示,這基本上也是Android的某個模塊實現各個層次的經典架構。

img

Android應用程序通過Framework提供的機制操作;Java領域需要本地c/c++提供服務的地方,通過JNI實現;JNI調用底層庫;庫函數通過操作映射的設備文件操作設備,LINUX kernel中的Driver完成相應的操作。另外,拋開Java和JNI,LINUX上用戶域的c/c++程序,也可以通過操作設備文件來完成。

Android的LOG也是這樣實現的,並將在本系列文章中分別講述。應用程序通過android.util.Log里的各種靜態方法,輸出LOG信息

[系列之二中具體講述];Log通過JNI接口調用c/c++的實現,而本地實現的寫LOG,也基本就是寫信息到設備文件[系列之三中具體講述];

設備文件是Android為了LOG機制而寫的LINUX的一個輕量級的驅動logger[系列之四中具體講述];

LOG信息的顯示可以是Emulator/Device上運行的LogCat程序[系列之五中具體講述];

另外,Android的本地實現庫也可利用現有機制,在c/c++的空間 直接輸出LOG[系列之六中具體講述]。

JAVA域輸出LOG

原文(有刪改):https://blog.csdn.net/thl789/article/details/6629914

LOG輸出幫助類

Android的Java程序通過android.util.Log類來輸出Log,下圖列出了我們常用的Log的靜態方法。

一般,要輸出Log信息,可直接調用Log.v()/Log.d()/Log.i()/Log.w()/Log.e()等類方法。這里之所以有這么多有區分的方法,這也是Log的分類。

android.util.Log常用的方法有以下5個:Log.v() Log.d() Log.i() Log.w() 以及 Log.e() 。

根據首字母對應VERBOSE,DEBUG,INFO, WARN,ERROR。

1、Log.v 的調試顏色為黑色的,任何消息都會輸出,這里的v代表verbose啰嗦的意思;
2、Log.d的輸出顏色是藍色的,僅輸出debug調試的意思,但他會輸出上層的信息,過濾起來可以通過DDMS的Logcat標簽來選擇
3、Log.i的輸出為綠色,一般提示性的消息information,它不會輸出Log.v和Log.d的信息,但會顯示i、w和e的信息
4、Log.w的意思為橙色,可以看作為warning警告,一般需要我們注意優化Android代碼,同時選擇它后還會輸出Log.e的信息
5、Log.e為紅色,可以想到error錯誤,這里僅顯示紅色的錯誤信息,這些錯誤就需要我們認真的分析,查看棧的信息了

Log的分類就如同Log的靜態常量成員定義的那樣,而Log的優先級按照數字大小排列,數字大的優先級高。而Log.wtf()記錄的則是非常致命的FAULT信息(What a Terrible Failure),報這個錯誤,不光是在Log里記錄,還要在界面上有提示,並可能殺死當前的進程。

What a Terrible Failure: Report a condition that should never happen. The error will always be logged at level ASSERT with the call stack. Depending on system configuration, a report may be added to the DropBoxManager and/or the process may be terminated immediately with an error dialog.

用來打印正常情況下永遠不會發生bug

有了這些分類,如果要輸出的LOG優先級低於當前設置的優先級,則該Log信息不會顯示。

一般的,在Java程序中用Log的方法打印Log之前,應先用isLoggable()判斷一下,該級別是否能被記錄。

另外,用Log.println()能達到與Log.xx()等方法同樣的輸出效果,只是在用它時,要指定對應的優先級。

類Log的實現

類android.util.Log的實現是比較簡單的。

類android.util.Log的構造函數是私有的,並不會被實例化,只是提供了靜態的屬性和方法。

而android.util.Log的各種Log記錄方法的實現都依賴於native的實現println_native(),Log.v()/Log.d()/Log.i()/Log.w()/Log.e()最終都是調用了println_native()。如Log.d()的實現:

public static int d(String tag, String msg) {

    return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}

Native方法println_native()是通過JNI在c/c++中實現的。

JNI及c/c++域寫設備文件

原文(有刪改):https://blog.csdn.net/thl789/article/details/6629919

類Log的JNI實現

由前文知道,類android.util.Log有兩個Native方法,需要通過JNI在c/c++中實現。

public static native boolean isLoggable(String tag, int level);

public static native int println_native(int bufID,
                                        int priority, String tag, String msg);

這兩個方法是在frameworks/base/core/jni/android_util_log.cpp中實現的。如何實現JNI的,在這里不做表述。不過最終這兩個方法分別轉入了下列兩個c/c++函數的調用。

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
 
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)

isLoggable()的實現

isLoggable的實現是比較<level>(來自參數)與當前property里設定的“log.tag.<tag>”(<tag>來自參數)的值,大於或等於都是可記錄的。

程序實現片斷如下:

    // LOG_NAMESPACE : “log.tag.”
    // chars: convert from param<tag>
    strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
    strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
  
    len = property_get(key, buf, "");
    int logLevel = toLevel(buf);
 
    return (logLevel >= 0 && level >= logLevel) ? true : false;

println_native()的實現

函數android_util_Log_println_native() [文件android_util.Log.cpp中]調用了__android_log_buf_write()[文件system/core/liblog/logd_write.c中]。__android_log_buf_write()組織了參數,又調用了write_to_log這個函數指針。

write_to_log這個函數指針是實現的關鍵。

看write_to_log的定義:

static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;

write_to_log初始是指向__write_to_log_init()這個函數的。所以第一次執行write_to_log的時候是執行了__write_to_log_init()。而如果write_to_log不是第一次被執行,它已經在__write_to_log_init()里被修改指向了__write_to_log_kernel()。

先看__write_to_log_init()的實現:

static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#ifdef HAVE_PTHREADS
    pthread_mutex_lock(&log_init_lock);
#endif
 
    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_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];
        }
    }
 
#ifdef HAVE_PTHREADS
    pthread_mutex_unlock(&log_init_lock);
#endif
 
    return write_to_log(log_id, vec, nr);
}

基本上就是做互斥訪問的保護,然后如果是第一次調用(write_to_log還指向__write_to_log_init()),就打開相應的設備文件,獲取描述符,並把write_to_log指向__write_to_log_kernel()。再在__write_to_log_kernel()中具體執行寫入文件操作。

看__write_to_kernel()的實現,基本就是寫操作:

static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#ifdef HAVE_PTHREADS
    pthread_mutex_lock(&log_init_lock);
#endif
 
    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_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];
        }
    }
 
#ifdef HAVE_PTHREADS
    pthread_mutex_unlock(&log_init_lock);
#endif
 
    return write_to_log(log_id, vec, nr);
}

總結一下,println_native()的操作,就是打開設備文件(如果還沒打開),然后寫入數據。而具體怎么寫入的,要看Log的設備驅動Logger的實現。

LOG設備驅動Logger

原文(有刪改):https://blog.csdn.net/thl789/article/details/6632736

Log的驅動是在kernel/drivers/staging/android/Logger.c中實現的。

初始化

看一個LINUX驅動,先看它如何初始化的。

static int __init init_log(struct logger_log *log)
{
        int ret;
 
        ret = misc_register(&log->misc);
        if (unlikely(ret)) {
                printk(KERN_ERR "logger: failed to register misc "
                                "device for log '%s'!\n", log->misc.name);
                return ret;
        }
 
        printk(KERN_INFO "logger: created %luK log '%s'\n",
                       (unsigned long) log->size >> 10, log->misc.name);
 
        return 0;
}
 
static int __init logger_init(void)
{
        int ret;
 
        ret = init_log(&log_main);
        if (unlikely(ret))
                goto out;
 
        ret = init_log(&log_events);
        if (unlikely(ret))
                goto out;
 
        ret = init_log(&log_radio);
        if (unlikely(ret))
                goto out;
 
        ret = init_log(&log_system);
        if (unlikely(ret))
                goto out;
 
out:
        return ret;
}
 
device_initcall(logger_init);

整個Logger驅動的入口點就是Logger_init(),它用init_log(struct logger_log *log)初始化了log_main, log_events, log_radiolog_system四個logger_log類型的結構,而這四個結構變量分別記錄着log的四個存儲體。Logger從這四個變量實現了同種設備的四個驅動,而log的驅動是MISC類型的驅動,通過misc_register()向系統注冊。四次注冊之后,它們對應的MINOR ID將是不同的,Looger也是通過minor來區分是哪一個驅動的。

static struct logger_log *get_log_from_minor(int minor)
{
    if (log_main.misc.minor == minor)
        return &log_main;
    if (log_events.misc.minor == minor)
        return &log_events;
    if (log_radio.misc.minor == minor)
        return &log_radio;
    if (log_system.misc.minor == minor)
        return &log_system;
    return NULL;
}

本文將以log_main來講解Logger驅動的實現。

關鍵數據結構

上節中,提到了log_main這個結構體變量,現在來看它的定義。

Log_main里保存了Logger操作必須的變量。buffer指向的真是一個靜態數組,用來存放用來讀寫的數據,Logger用它組成了一個邏輯上的循環隊列,寫者可以往w_off指向的地方寫東西,而一旦有內容,會通知等待隊列wq里的讀者們來讀取內容。因為buffer實現的是循環隊列,所以buffer的大小size經常用來做除高位的運算,一定要是一個2次冪的數字。mutex用來保護log_main這個關鍵資源的。Logger是MISC類型的驅動,它保留着一個miscdevice類型的變量misc。misc里面也有最為關鍵的file_operations結構,這正是應用程序通過文件操作,與驅動打交道的入口。

Logger實現的功能

從上面log_main的類型定義就能看出,Logger實現了什么。一句話概括Logger就是實現了讀寫者,並實現同步操作。不過,Logger的讀寫者有些特殊,寫者寫操作不會被阻塞,也不會寫滿溢出,也就是寫時只要有內容可以不停的寫,超出Buffer就覆蓋舊的[與應用程序具體的寫操作結合來看];讀者因為要讀的內容為空就會被阻塞掛起,而一旦有內容,所有被掛起的讀者都會被喚醒[與應用程序具體的讀操作結合來看]。

下面看具體實現的時候,就分別從讀者和寫者的角度去看。

寫者的實現

看二小節圖中的關鍵結構logger_fops: file_operations,寫者的關鍵實現就看open、release和write這幾個函數的實現了,它們被分別賦值給了logger_open() / logger_release() / logger_aio_write()。

logger_open()為寫者做的工作就是,通過minor id獲得logger_log的實例,然后賦值給函數參數中傳遞進來的file的private_data中。

logger_release()不需要為寫者做的什么工作。

logger_poll()因為寫不需要被阻塞。所以這里檢測到是因為非因為讀而打開的文件(!(file->f_mode &FMODE_READ))時,就直接返回POLLOUT | POLLWRNORM。無論怎樣都可寫。

logger_aio_write()是寫數據(也就是log信息)的關鍵。這里是通過異步IO的方法,應用程序通過write()/writev()和aio_write()時都能調用到這個方法。

記錄log信息時,寫log用的接口是writev(),寫的是vec形式的數據,這邊寫的過程中來的當然也是vec數據了,另外,寫具體之間,還寫入了類型為logger_entry的數據,來記錄時間等信息。寫數據到具體buffer時因為存儲的位置可能不是連續的,而寫在buffer的結尾和開頭位置,所以要做判斷,並可能要有兩次寫的buffer的動作。參數里的數據來自用戶空間,不能在內核空間直接使用,要用copy_from_user()。寫完之后,用wake_up_interruptible(&log->wq)喚醒所有在掛起等待的讀者。

讀者的實現

看二小節圖中的關鍵結構logger_fops: file_operations,寫者的關鍵實現就看open、release和read這幾個函數的實現了,它們被分別賦值給了logger_open() / logger_release() / logger_read()。

logger_open() 為讀者做的工作就是,通過minor id獲得logger_log的實例,然后動態申請一個logger_reader類型的讀者,並把它加入到logger_log的讀者列表readers的結尾,再賦值給函數參數中傳遞進來的file的private_data中。

logger_release() 與logger_open()對應,將這個讀者從讀者列表logger_log.readers中移除,並釋放掉這個動態申請的實例。

logger_poll()因為應用讀之前會調用poll()/select()查看是否可以寫。所以這里會用poll_wait()把參數中的poll_table加入到logger_log.wq中,並且如果有內容可讀,才設置可讀標志|= POLLIN |POLLRDNORM。

logger_read() 是讀數據(也就是log信息)的關鍵。

讀數據之前,要先保證有數據,否則該讀者就要被掛起在logger_log的等待隊列wq上。從具體buffer讀數據到時因為存儲的位置可能不是連續的,存儲在buffer的結尾和開頭位置,所以要做判斷,並可能要有兩次讀去buffer的動作。數據來自內核空間,要通過用戶空間的參數里傳遞出去,需要copy_to_user()。

循環隊列的實現

這個是數據結構里最經典的案例了,這里不再具體解釋如何實現,只是列出重要結構,只是希望讀者還記得數據結構里邏輯結構和物理結構的說法。

隊列大小:log_main.size

寫頭:log_main.w_off

讀頭:logger_reader.r_off

隊列為空判斷:log_main.w_off == logger_reader.r_off

隊列為滿判斷:不需要

ioctl的實現

Logger提供給應用程序通過ioctl()來獲取信息或控制LOGbuffer的功能。Logger是把logger_ioctl通過file_operations注冊到文件系統中來實現這一功能的。Logger_ioctl()提供了下列ioctl控制命令:LOGGER_GET_LOG_BUF_SIZE / LOGGER_GET_LOG_LEN/ LOGGER_GET_NEXT_ENTRY_LEN / LOGGER_FLUSH_LOG。實現很簡單:

LOGGER_GET_LOG_BUF_SIZE獲取Buffer的大小,直接返回logger_log.size即可;

LOGGER_GET_LOG_LEN只對讀有效,獲取當前LOG的大小,存儲連續的話就是log->w_off -reader->r_off,否則就是(log->size -reader->r_off) + log->w_off;

LOGGER_GET_NEXT_ENTRY_LEN獲取Entry的長度,只對讀有效。

LOGGER_FLUSH_LOG只對寫打開有效。所謂FLUSH LOG,直接重置每個reader的r_off,並設置新reader要訪問用的head即可。

從前文知道,LOG被寫入到了驅動的節點,那如何獲取這些LOG信息並呈現出來的呢?ANDROID里是有個叫LogCat的應用程序被用來獲取LOG信息。LogCat不僅從設備節點處獲取LOG,並且還提供了很多選項供用戶來過濾、控制輸出格式等。本文只講解如何獲取LOG部分,相關的LogCat的使用方式,可參考Android的Logcat命令詳解。

LogCat是在文件system/core/logcat/logcat.cpp中實現的。

獲取LOG的應用程序LogCat

從Logger設備驅動的實現知道,Log的讀取是阻塞的操作,亦即,有數據可用,讀出數據;否則,讀操作會被BLOCK,相應的讀進程也會被掛起等待。

下面看應用程序LogCat中如何實現讀的,這可能需要不斷回頭與寫操作和驅動實現結合來看。

看具體實現之前,先看一個logcat中定義的重要的結構體log_device_t。其中的重要的成員在后面用到的時候再具體解釋。

打開設備節點

Android的Logcat命令詳解的命令參數-b <buffer>知道,logcat是可以通過參數來指定對哪個buffer(main/radio/event)進行操作的。Logcat的b參數解析的地方,是通過傳遞進來的參數(main/radio/event)來創建了一個上面的結構變量,而這些結構通過log_device_t.next鏈接起來。

                if (devices) {
                    dev = devices;
                    while (dev->next) {
                        dev = dev->next;
                    }
                    dev->next = new log_device_t(buf, binary, optarg[0]);
                } else {
                    devices = new log_device_t(buf, binary, optarg[0]);
                }

而創建實例的時候的參數被保留了下來,用於后續操作。

是由LOG_FILE_DIR和optarg(-b參數)組合在一起的(為:“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”),保留在device: char*;

保留在binary: bool;

<optarg[0]>是-b參數的第一個字符,保存在label: char中。

好了,下面就有了打開設備節點時的參數:

dev->fd = open(dev->device, mode);

dev->device根據-b的參數可能為“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”;

mode缺省時為O_RDONLY,讀取。只要在運行logcat時,用了-c參數清除log時才以O_WRONLY打開。

而打開文件的文件操作符保存在log_device_t的fd域中,用於后續的操作。

獲取Log的操作都是在readLogLines(log_device_t* devices)中實現的。

因為logcat可能會同時操作多個Buffer,而read()會阻塞讀取進程,對其他Buffer的讀取就不能進行,所以這里用select()來判斷可讀取的Buffer。

二、select選取可讀取的Buffer

Logcat把log_device_t中的所有的buffer的文件操作符dev->fd,都放在readset中[line#7],做為select()的里的<readfds: fd_set*>讀參數,來獲取可讀取的Buffer。這樣當任何一個Buffer上有LOG數據時,select()都會返回。當然等待過程中也忽略掉其他signal的影響。相應的代碼如下:

       fd_set readset; 
 
       do {
            timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
            FD_ZERO(&readset);
            for (dev=devices; dev; dev = dev->next) {
                FD_SET(dev->fd, &readset);
            }
            result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
        } while (result == -1 && errno == EINTR);

三、讀LOG操作

select()返回之后,通過循環判定dev->fd是否在readset里被設置(FD_ISSET)[line#3],知道哪個log buffer里已經有數據了。

        if (result >= 0) {
            for (dev=devices; dev; dev = dev->next) {
                if (FD_ISSET(dev->fd, &readset)) {
                    queued_entry_t* entry = new queued_entry_t();
                    /* NOTE: driver guarantees we read exactly one full entry */
                    ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
 
        //…

通過read()讀取[line#6]已經有數據的LOG Buffer的文件操作符dev->fd就可得到新到來的log了。

應用程序logcat中已經獲取了LOG信息,接下來對數據的處理就都可以在這里進行了,可以過濾,寫文件,格式化輸入等操作。詳細的logcat的命令參數可參見Android的Logcat命令詳解.

c/c++域使用LOG

c/c++本地庫中實現LOG輸出

通過前面的文章知道Android的Java中通過android.util.Log輸出Log信息,那Android的本地c/c++程序能不能也通過這樣的機制來記錄Log呢?再回頭看Log現有的c/c++的本地實現,答案當然是肯定的,而且是相當簡單。Android直接在頭文件(system/core/include/cutils/log.h)里定義了一些宏就可以很好的實現了。

因為,LOG分了VERBOSE/DEBUG/INFO/WARN/ERROR/ASSERT等類別,簡單起見,以DEBUG為例的實現來說明。

#ifndef LOGD
#define LOGD(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#endif
 
#ifndef LOGD_IF
#define LOGD_IF(cond, ...) \
    ( (CONDITION(cond)) \
    ? LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__) \
    : (void)0 )
#endif
 
#ifndef LOG
#define LOG(priority, tag, ...) \
    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
 
#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...)                                     \
    ({                                                                  \
       if (((priority == ANDROID_LOG_VERBOSE) && (LOG_NDEBUG == 0)) ||  \
           ((priority == ANDROID_LOG_DEBUG) && (LOG_NDDEBUG == 0))  ||  \
           ((priority == ANDROID_LOG_INFO) && (LOG_NIDEBUG == 0))   ||  \
            (priority == ANDROID_LOG_WARN)                          ||  \
            (priority == ANDROID_LOG_ERROR)                         ||  \
            (priority == ANDROID_LOG_FATAL))                            \
                (void)android_printLog(priority, tag, __VA_ARGS__);     \
    })
#endif
 
#define android_printLog(prio, tag, fmt...) \
__android_log_print(prio, tag, fmt)

而這一系列宏,最后還是用到了函數__android_log_print()

int __android_log_print(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_write(prio, tag, buf);
}

這里還是調到了函數__android_log_write()。這個函數應該很熟悉吧,正是前文敘及的c/c++本地函數實現寫設備文件的地方。

c/c++程序中記錄Log的做法

要在c/c++中記錄Log通常的做法是:

定義自己的TAG_LOG宏;包含頭文件log.h;然后在需要記錄Log的地方直接用LOGV/LOGD/LOGI/LOGW/LOGE即可。

比如,文件lights.c中就在開頭這樣寫,

#define LOG_TAG "lights"
#include <cutils/log.h>

然后在該文件的后續部分,大量的用了LOGV/LOGE, etc來記錄LOG。

1、 目的:

為了規范軟件工程師在android代碼編寫過程中輸出Log的行為,使得發布的產品中打印的Log是必須的,打印的Log的級別是能真實反映此Log對應的級別,標簽、Log內容具有很好的可讀性。

2、 適用范圍

android平台java、c++、c代碼編寫。

3、 Log的調用及等級介紹

(1)、Log的等級有Verbose,Debug,Info,Warn,Error。

(2)、java層調用:在java層調用import android.util.Log,在需要打印Log的地方執行Log.v,Log.d,Log.i,Log.w,Log.e.

(3)、c、c++層調用:在c,c++層包含此頭文件:#include <cutils/log.h>,在需要調用Log的地方執行:ALOGV,ALOGD,ALOGI,ALOGW,ALOGE。

(4)、各個Log等級的使用

Verbose: 開發調試過程中一些詳細信息,不應該編譯進產品中,只在開發階段使用。(參考api文檔的描述:Verbose should never be compiled into anapplication except during development)

Debug: 用於調試的信息,編譯進產品,但可以在運行時關閉。(參考api文檔描述:Debug logs are compiled in but stripped atruntime)

Info:例如一些運行時的狀態信息,這些狀態信息在出現問題的時候能提供幫助。

Warn:警告系統出現了異常,即將出現錯誤。

Error:系統已經出現了錯誤。

Info、Warn、Error這三個等級的Log的警示作用依次提高,需要一直保留。這些信息在系統異常時能提供有價值的分析線索。

4、 具體規則

(1)、Verbose等級的Log,請不要在user版本中出現。Verbose級別的Log輸出參見下面例子。

示例Java部分:

import android.os.Build;

import android.util.Log

final public Boolean isEng =Build.TYPE.equals("eng");

if (isEng)

Log.v(“LOG_TAG”,“LOG_MESSAGE”);

示例c、c++部分:

#include<cutils/log.h>

char value[PROPERTY_VALUE_MAX];

int isEng=0;

property_get("ro.build.type",value, "user");

isEng=strcmp(value, "eng");

if (isEng)

ALOGV();

(2)、Debug等級的log,默認不開啟,通過終端命令開啟。

Debug級別的log輸出參見下面例子。

示例Java部分:

import android.util.Log

final String TAG=”MyActivity”;

final public Boolean LOG_DEBUG = Log.isLoggable(TAG, Log.DEBUG);

if (LOG_DEBUG)

Log.d(“LOG_TAG”,“LOG_MESSAGE”);

運行時開啟log: 在終端輸入:setprop log.tag.MyActivity DEBUG

運行時關閉log: 在終端輸入:setprop log.tag.MyActivity INFO

示例c、c++部分:

#include<cutils/log.h>

#defineLOG_CTL “debug.MyActivity.enablelog”

charvalue[PROPERTY_VALUE_MAX];

int isDebug=0;

property_get(LOG_CTL,value, "0");

isDebug=strcmp(value,"1");

if (isDebug)

ALOGD();

運行時開啟log: 在終端輸入:setpropdebug.MyActivity.enablelog 1

運行時關閉log: 在終端輸入:setpropdebug.MyActivity.enablelog 0

(3)、Info、Warn、Error等級的Log禁止作為普通的調試信息使用,這些等級的Log是系統出現問題時候的重要分析線索,如果隨意使用,將給Log分析人員帶來極大困擾。請參考前面的等級介紹合理使用。

(4)、禁止使用new Exception("print trace").printStackTrace()或者Log. getStackTraceString(Exception)方式打印普通調試信息,因為這種方式打印Log非常消耗系統資源。此種方式打印Log一般只出現try..catch某個異常使用。

(5)、Log的tag命名,使用Activity名稱或者類、模塊的名稱,不要出現自己的姓名拼音或其他簡稱。在c++/c代碼中調用ALOGD等宏函數,參數沒有傳入tag,需要在文件頭部#define LOG_TAG"YOUR_TAG_NAME"。

(6)、Log的內容,不要出現公司名稱、個人名稱或相關簡稱,Log內容不要出現無意義的內容,如連續的等號或星號或連續的數字等,Log內容要方便其他分析Log的人員查看。

(7)、Log輸出的頻率需要控制,例如1s打印一次的Log,盡量只在eng版本使用,user版本如需開啟,請默認關閉,通過設置setprop命令來開啟。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM