轉自: http://my.oschina.net/wolfcs/blog/164624
Android log系統。
在android Java code中輸出log
android系統有4種類型、6個優先級的log,有一些常量用於標識這些信息,相關的定義在frameworks/base/core/Java/android/util/Log.java中可以看到:
01 |
/** |
02 |
* Priority constant for the println method; use Log.v. |
03 |
*/ |
04 |
public static final int VERBOSE = 2 ; |
05 |
06 |
/** |
07 |
* Priority constant for the println method; use Log.d. |
08 |
*/ |
09 |
public static final int DEBUG = 3 ; |
10 |
11 |
/** |
12 |
* Priority constant for the println method; use Log.i. |
13 |
*/ |
14 |
public static final int INFO = 4 ; |
15 |
16 |
/** |
17 |
* Priority constant for the println method; use Log.w. |
18 |
*/ |
19 |
public static final int WARN = 5 ; |
20 |
21 |
/** |
22 |
* Priority constant for the println method; use Log.e. |
23 |
*/ |
24 |
public static final int ERROR = 6 ; |
25 |
26 |
/** |
27 |
* Priority constant for the println method. |
28 |
*/ |
29 |
public static final int ASSERT = 7 ; |
30 |
31 |
/** @hide */ public static final int LOG_ID_MAIN = 0 ; |
32 |
/** @hide */ public static final int LOG_ID_RADIO = 1 ; |
33 |
/** @hide */ public static final int LOG_ID_EVENTS = 2 ; |
34 |
/** @hide */ public static final int LOG_ID_SYSTEM = 3 ; |
Java層可以通過三個class來輸出其中三種類型的log,三種類型分別為MAIN、RADIO和SYSTEM,三個class分別為Log、Rlog和Slog,其package則分別為android.util、android.telephony和 android.util。這些用於打印log的classes,其構造函數都為private,因而都不能創建其對象,但它們都提供了靜態方法來給用戶打印log。各個log打印class的實現都大同小異,可以看一下Log這個class中的一些:
01 |
public static int v(String tag, String msg, Throwable tr) { |
02 |
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); |
03 |
} |
04 |
05 |
/** |
06 |
* Send a {@link #DEBUG} log message. |
07 |
* @param tag Used to identify the source of a log message. It usually identifies |
08 |
* the class or activity where the log call occurs. |
09 |
* @param msg The message you would like logged. |
10 |
*/ |
11 |
public static int d(String tag, String msg) { |
12 |
return println_native(LOG_ID_MAIN, DEBUG, tag, msg); |
13 |
} |
最終都會是調用Log.println_native()靜態native方法來打印log,各個類中各個方法的不同之處也僅在於參數的差異。
Log.println_native()方法
這個方法的code在/frameworks/base/core/jni/android_util_Log.cpp,為:
01 |
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, |
02 |
jint bufID, jint priority, jstring tagObj, jstring msgObj) |
03 |
{ |
04 |
const char * tag = NULL; |
05 |
const char * msg = NULL; |
06 |
07 |
if (msgObj == NULL) { |
08 |
jniThrowNullPointerException(env, "println needs a message" ); |
09 |
return -1; |
10 |
} |
11 |
12 |
if (bufID < 0 || bufID >= LOG_ID_MAX) { |
13 |
jniThrowNullPointerException(env, "bad bufID" ); |
14 |
return -1; |
15 |
} |
16 |
17 |
if (tagObj != NULL) |
18 |
tag = env->GetStringUTFChars(tagObj, NULL); |
19 |
msg = env->GetStringUTFChars(msgObj, NULL); |
20 |
21 |
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); |
22 |
23 |
if (tag != NULL) |
24 |
env->ReleaseStringUTFChars(tagObj, tag); |
25 |
env->ReleaseStringUTFChars(msgObj, msg); |
26 |
27 |
return res; |
28 |
} |
29 |
30 |
/* |
31 |
* JNI registration. |
32 |
*/ |
33 |
static JNINativeMethod gMethods[] = { |
34 |
/* name, signature, funcPtr */ |
35 |
{ "isLoggable" , "(Ljava/lang/String;I)Z" , ( void *) android_util_Log_isLoggable }, |
36 |
{ "println_native" , "(IILjava/lang/String;Ljava/lang/String;)I" , ( void *) android_util_Log_println_native }, |
37 |
}; |
可以看到,干的都是轉換參數的事情,最終再call到__android_log_buf_write()函數,這個函數的定義在system/core/liblog/logd_write.c,為:
01 |
int __android_log_buf_write( int bufID, int prio, const char *tag, const char *msg) |
02 |
{ |
03 |
struct iovec vec[3]; |
04 |
char tmp_tag[32]; |
05 |
06 |
if (!tag) |
07 |
tag = "" ; |
08 |
09 |
/* XXX: This needs to go! */ |
10 |
if ((bufID != LOG_ID_RADIO) && |
11 |
(! strcmp (tag, "HTC_RIL" ) || |
12 |
! strncmp (tag, "RIL" , 3) || /* Any log tag with "RIL" as the prefix */ |
13 |
! strncmp (tag, "IMS" , 3) || /* Any log tag with "IMS" as the prefix */ |
14 |
! strcmp (tag, "AT" ) || |
15 |
! strcmp (tag, "GSM" ) || |
16 |
! strcmp (tag, "STK" ) || |
17 |
! strcmp (tag, "CDMA" ) || |
18 |
! strcmp (tag, "PHONE" ) || |
19 |
! strcmp (tag, "SMS" ))) { |
20 |
bufID = LOG_ID_RADIO; |
21 |
// Inform third party apps/ril/radio.. to use Rlog or RLOG |
22 |
snprintf(tmp_tag, sizeof (tmp_tag), "use-Rlog/RLOG-%s" , tag); |
23 |
tag = tmp_tag; |
24 |
} |
25 |
26 |
vec[0].iov_base = (unsigned char *) &prio; |
27 |
vec[0].iov_len = 1; |
28 |
vec[1].iov_base = ( void *) tag; |
29 |
vec[1].iov_len = strlen (tag) + 1; |
30 |
vec[2].iov_base = ( void *) msg; |
31 |
vec[2].iov_len = strlen (msg) + 1; |
32 |
33 |
return write_to_log(bufID, vec, 3); |
34 |
} |
做了三件事情,一是根據log的tag,轉換bufID,二是用傳進來的參數構造一個struct iovec數組,三是將前一步構造的數組作為參數調用write_to_log()。write_to_log()是一個函數指針,在開始時,它指向了__write_to_log_init():
1 |
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init; |
__write_to_log_init()的實現如下:
01 |
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr) |
02 |
{ |
03 |
#ifdef HAVE_PTHREADS |
04 |
pthread_mutex_lock(&log_init_lock); |
05 |
#endif |
06 |
07 |
if (write_to_log == __write_to_log_init) { |
08 |
log_fds[LOG_ID_MAIN] = log_open( "/dev/" LOGGER_LOG_MAIN, O_WRONLY); |
09 |
log_fds[LOG_ID_RADIO] = log_open( "/dev/" LOGGER_LOG_RADIO, O_WRONLY); |
10 |
log_fds[LOG_ID_EVENTS] = log_open( "/dev/" LOGGER_LOG_EVENTS, O_WRONLY); |
11 |
log_fds[LOG_ID_SYSTEM] = log_open( "/dev/" LOGGER_LOG_SYSTEM, O_WRONLY); |
12 |
13 |
write_to_log = __write_to_log_kernel; |
14 |
15 |
if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 || |
16 |
log_fds[LOG_ID_EVENTS] < 0) { |
17 |
log_close(log_fds[LOG_ID_MAIN]); |
18 |
log_close(log_fds[LOG_ID_RADIO]); |
19 |
log_close(log_fds[LOG_ID_EVENTS]); |
20 |
log_fds[LOG_ID_MAIN] = -1; |
21 |
log_fds[LOG_ID_RADIO] = -1; |
22 |
log_fds[LOG_ID_EVENTS] = -1; |
23 |
write_to_log = __write_to_log_null; |
24 |
} |
25 |
26 |
if (log_fds[LOG_ID_SYSTEM] < 0) { |
27 |
log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN]; |
28 |
} |
29 |
} |
30 |
31 |
#ifdef HAVE_PTHREADS |
32 |
pthread_mutex_unlock(&log_init_lock); |
33 |
#endif |
34 |
35 |
return write_to_log(log_id, vec, nr); |
36 |
} |
這個地方,會檢查write_to_log是否指向了__write_to_log_init,也就是是否是第一次打印log,如果是,則打開幾個用於輸出log的設備文件,然后使write_to_log函數指針指向__write_to_log_kernel,或者在打開輸出log設備文件出現異常時,使write_to_log指向__write_to_log_null,最后再次調用經過了重定向的write_to_log,也就是__write_to_log_kernel或者__write_to_log_null函數。我們可以看一下那幾個設備文件究竟是什麽(在system/core/include/cutils/logger.h):
1 |
#define LOGGER_LOG_MAIN "log/main" |
2 |
#define LOGGER_LOG_RADIO "log/radio" |
3 |
#define LOGGER_LOG_EVENTS "log/events" |
4 |
#define LOGGER_LOG_SYSTEM "log/system" |
接着繼續來看__write_to_log_kernel或者__write_to_log_null函數:
01 |
static int __write_to_log_null(log_id_t log_fd, struct iovec *vec, size_t nr) |
02 |
{ |
03 |
return -1; |
04 |
} |
05 |
06 |
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr) |
07 |
{ |
08 |
ssize_t ret; |
09 |
int log_fd; |
10 |
11 |
if ( /*(int)log_id >= 0 &&*/ ( int )log_id < ( int )LOG_ID_MAX) { |
12 |
log_fd = log_fds[( int )log_id]; |
13 |
} else { |
14 |
return EBADF; |
15 |
} |
16 |
17 |
do { |
18 |
ret = log_writev(log_fd, vec, nr); |
19 |
} while (ret < 0 && errno == EINTR); |
20 |
21 |
return ret; |
22 |
} |
由log_id獲取到對應的log_fd,然后調用log_writev()打印log。可以看一下log_writev()的定義,它是一個宏:
01 |
#if FAKE_LOG_DEVICE |
02 |
// This will be defined when building for the host. |
03 |
#define log_open(pathname, flags) fakeLogOpen(pathname, flags) |
04 |
#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count) |
05 |
#define log_close(filedes) fakeLogClose(filedes) |
06 |
#else |
07 |
#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC) |
08 |
#define log_writev(filedes, vector, count) writev(filedes, vector, count) |
09 |
#define log_close(filedes) close(filedes) |
10 |
#endif |
這些就都是標准的unix系統調用了。
本地層代碼Log輸出
以一些比較典型的native代碼打印log的case為例。先來看一下,在JNI的code中打印log的方法。在JNI中,比較常見到用ALOGx這一組宏來打印log,比如在frameworks/base/core/jni/android/graphics/TextLayoutCache.cpp這個文件中的dumpCacheStats()函數:
01 |
void TextLayoutCache::dumpCacheStats() { |
02 |
float remainingPercent = 100 * ((mMaxSize - mSize) / (( float )mMaxSize)); |
03 |
float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; |
04 |
05 |
size_t cacheSize = mCache.size(); |
06 |
07 |
ALOGD( "------------------------------------------------" ); |
08 |
ALOGD( "Cache stats" ); |
09 |
ALOGD( "------------------------------------------------" ); |
10 |
ALOGD( "pid : %d" , getpid()); |
11 |
ALOGD( "running : %.0f seconds" , timeRunningInSec); |
12 |
ALOGD( "entries : %d" , cacheSize); |
13 |
ALOGD( "max size : %d bytes" , mMaxSize); |
14 |
ALOGD( "used : %d bytes according to mSize" , mSize); |
15 |
ALOGD( "remaining : %d bytes or %2.2f percent" , mMaxSize - mSize, remainingPercent); |
16 |
ALOGD( "hits : %d" , mCacheHitCount); |
17 |
ALOGD( "saved : %0.6f ms" , mNanosecondsSaved * 0.000001f); |
18 |
ALOGD( "------------------------------------------------" ); |
19 |
} |
使用這組宏,需要定義另外一個宏來作為所打印log的tag:
1 |
#define LOG_TAG "TextLayoutCache" |
此外,還要include頭文件<cutils/log.h>。來看一下這些宏中的一些的定義:
01 |
/* |
02 |
* Simplified macro to send a debug log message using the current LOG_TAG. |
03 |
*/ |
04 |
#ifndef ALOGD |
05 |
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) |
06 |
#endif |
07 |
08 |
/* |
09 |
* Simplified macro to send a warning log message using the current LOG_TAG. |
10 |
*/ |
11 |
#ifndef ALOGW |
12 |
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) |
13 |
#endif |
14 |
15 |
/* |
16 |
* Basic log message macro. |
17 |
* |
18 |
* Example: |
19 |
* ALOG(LOG_WARN, NULL, "Failed with error %d", errno); |
20 |
* |
21 |
* The second argument may be NULL or "" to indicate the "global" tag. |
22 |
*/ |
23 |
#ifndef ALOG |
24 |
#define ALOG(priority, tag, ...) \ |
25 |
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) |
26 |
#endif |
27 |
28 |
/* |
29 |
* Log macro that allows you to specify a number for the priority. |
30 |
*/ |
31 |
#ifndef LOG_PRI |
32 |
#define LOG_PRI(priority, tag, ...) \ |
33 |
android_printLog(priority, tag, __VA_ARGS__) |
34 |
#endif |
35 |
36 |
#define android_printLog(prio, tag, fmt...) \ |
37 |
__android_log_print(prio, tag, fmt) |
先來看一下,在native層中定義的priority(在system/core/include/android/log.h中):
01 |
/* |
02 |
* Android log priority values, in ascending priority order. |
03 |
*/ |
04 |
typedef enum android_LogPriority { |
05 |
ANDROID_LOG_UNKNOWN = 0, |
06 |
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ |
07 |
ANDROID_LOG_VERBOSE, |
08 |
ANDROID_LOG_DEBUG, |
09 |
ANDROID_LOG_INFO, |
10 |
ANDROID_LOG_WARN, |
11 |
ANDROID_LOG_ERROR, |
12 |
ANDROID_LOG_FATAL, |
13 |
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ |
14 |
} android_LogPriority; |
另外,這些宏最終都會call到__android_log_print(),也是在system/core/liblog/logd_write.c中:
01 |
int __android_log_print( int prio, const char *tag, const char *fmt, ...) |
02 |
{ |
03 |
va_list ap; |
04 |
char buf[LOG_BUF_SIZE]; |
05 |
06 |
va_start (ap, fmt); |
07 |
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); |
08 |
va_end (ap); |
09 |
10 |
return __android_log_write(prio, tag, buf); |
11 |
} |
先是格式化參數,然后就是調用__android_log_write()函數。這個函數的code如下:
01 |
int __android_log_write( int prio, const char *tag, const char *msg) |
02 |
{ |
03 |
struct iovec vec[3]; |
04 |
log_id_t log_id = LOG_ID_MAIN; |
05 |
char tmp_tag[32]; |
06 |
07 |
if (!tag) |
08 |
tag = "" ; |
09 |
10 |
/* XXX: This needs to go! */ |
11 |
if (! strcmp (tag, "HTC_RIL" ) || |
12 |
! strncmp (tag, "RIL" , 3) || /* Any log tag with "RIL" as the prefix */ |
13 |
! strncmp (tag, "IMS" , 3) || /* Any log tag with "IMS" as the prefix */ |
14 |
! strcmp (tag, "AT" ) || |
15 |
! strcmp (tag, "GSM" ) || |
16 |
! strcmp (tag, "STK" ) || |
17 |
! strcmp (tag, "CDMA" ) || |
18 |
! strcmp (tag, "PHONE" ) || |
19 |
! strcmp (tag, "SMS" )) { |
20 |
log_id = LOG_ID_RADIO; |
21 |
// Inform third party apps/ril/radio.. to use Rlog or RLOG |
22 |
snprintf(tmp_tag, sizeof (tmp_tag), "use-Rlog/RLOG-%s" , tag); |
23 |
tag = tmp_tag; |
24 |
} |
25 |
26 |
vec[0].iov_base = (unsigned char *) &prio; |
27 |
vec[0].iov_len = 1; |
28 |
vec[1].iov_base = ( void *) tag; |
29 |
vec[1].iov_len = strlen (tag) + 1; |
30 |
vec[2].iov_base = ( void *) msg; |
31 |
vec[2].iov_len = strlen (msg) + 1; |
32 |
33 |
return write_to_log(log_id, vec, 3); |
34 |
} |
這個函數與我們前面看到的__android_log_buf_write()非常相似。所不同的就是這個函數沒有log_id參數,因而它默認是輸出MAIN log,當log的TAG為某些特殊字串時,則輸出RADIO log。最后同樣是調用write_to_log這個函數指針來輸出log。
我們再來看一個skia里面打log的SkDebugf()函數的實現:
1 |
#include <android/log.h> |
2 |
3 |
void SkDebugf( const char format[], ...) { |
4 |
va_list args; |
5 |
va_start (args, format); |
6 |
__android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args); |
7 |
va_end (args); |
8 |
} |
call到了__android_log_vprint()來輸出log,__android_log_vprint()的定義也在system/core/liblog/logd_write.c中:
1 |
int __android_log_vprint( int prio, const char *tag, const char *fmt, va_list ap) |
2 |
{ |
3 |
char buf[LOG_BUF_SIZE]; |
4 |
5 |
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); |
6 |
7 |
return __android_log_write(prio, tag, buf); |
8 |
} |
一樣是__android_log_write()函數。
Done.