Java native日志系统_Android Logger日志系统

文件夹

前言

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

执行时库层日志库——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 {

union {

unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));

struct logger_entry entry __attribute__((aligned(4)));

};

queued_entry_t* next;

queued_entry_t() {

next = NULL;

}

};

结构体queued_entry_t用来描写叙述一个日志记录队列。

每一种类型的日志记录都相应有一个日志记录队列。

接下来。再来看一下日志设备的结构体log_device_t。源代码例如以下:

struct log_device_t {

char* device;

bool binary;

int fd;

bool printed;

char label;

queued_entry_t* queue;

log_device_t* next;

log_device_t(char* d, bool b, char l) {

device = d;

binary = b;

label = l;

queue = NULL;

next = NULL;

printed = false;

}

void enqueue(queued_entry_t* entry) {

if (this->queue == NULL) {

this->queue = entry;

} else {

queued_entry_t** e = &this->queue;

while (*e && cmp(entry, *e) >= 0) }{

e = &((*e)->next);

}

entry->next = *e;

*e = entry;

}

}

};

结构体log_device_t用来描写叙述一个日志设备。

当中:

成员变量device保存的是日志设备文件名。Logger日志驱动程序初始化时。会创建四个设备文件/dev/log/main、/dev/log/system、/dev/log/radio和/dev/log/events分别代表四个日志设备。

成员变量label用来描写叙述日志设备的标号,当中,日志设备/dev/log/main、/dev/log/system、/dev/log/radio、/dev/log/events相应的标号分别为m、s、r、e。

成员binary是一个布尔值,表示日志记录的内容是否是二进制格式的。

眼下仅仅有/dev/log/events记录的是二进制格式。

成员变量fd是一个文件描写叙述符,它是调用函数open来打开相应的日志设备文件得到的,用来从logger日志驱动程序中读取日志记录。

成员变量printed是一个布尔值。用来表示一个日志设备是否已经处于输出状态。

成员变量queue用来保存日志设备中的日志记录。

成员变量next用来连接下一个日志设备。

成员函数enqueue用来将一条日志记录加入到内部的日志记录队列中。每次往队列中加入一条日志记录时,都会依据它的写入时间来找到它在队列中的位置,然后将它插入队列中。写入的时间比較是通过cmp函数实现的。

static int cmp(queued_entry_t* a, queued_entry_t* b)

{

int n = a->entry.sec - b->entry.sec;

if (n != 0) {

return n;

}

return a->entry.nsec - b->entry.nsec;

}

事实上。我觉得cpm函数没什么好解释的,真正有意思的是enqueue函数的实现。这里使用了一个技巧,通过二级指针来降低变量声明(ps:通常我们在做链表插入操作的时候。通常会维护两个指针)。

二级指针的精髓在于,能够让我们改动指针的值。(ps:想要改动指针的值,就须要改动指针的指针)。

真正日志打印的时候。须要转换成AndroidLogEntry结构体,相关的结构体定义例如以下:

typedef struct AndroidLogEntry_t {

time_t tv_sec;

long tv_nsec;

android_LogPriority priority;

int msg_type;

int32_t pid;

int32_t tid;

const char *tag;

size_t messageLen;

const char *message;

} AndroidLogEntry;

typedef enum android_LogPriority {

ANDROID_LOG_UNKNOWN = 0,

ANDROID_LOG_DEFAULT,

ANDROID_LOG_VERBOSE,

ANDROID_LOG_DEBUG,

ANDROID_LOG_INFO,

ANDROID_LOG_WARN,

ANDROID_LOG_ERROR,

ANDROID_LOG_FATAL,

ANDROID_LOG_SILENT,

} android_LogPriority;

同一时候,另一个结构体FilterInfo_t用来描写叙述日志记录输出过滤器。源代码例如以下:

typedef struct FilterInfo_t {

char *mTag;

android_LogPriority mPri;

struct FilterInfo_t *p_next;

} FilterInfo;

成员变量mTag和mPri分别表示要过滤的日志记录的标签和优先级。

当一条日志记录的标签等于mTag时,假设它的优先级大于等于mPri。那么它就会被输出。否则就会被忽略。成员变量p_next用来连接下一个日志输出过滤器。

最后,再介绍AndroidLogFormat_t结构体。

struct AndroidLogFormat_t {

android_LogPriority global_pri;

FilterInfo *filters;

AndroidLogPrintFormat format;

};

从结构体定义上就能够知道,这个结构体是用来保存日志记录的输出格式以及输出过滤器的。

初始化过程

Logcat工具的初始化过程是从文件logcat.cpp中的main函数開始的,它会打开日志设备和解析命令行參数。因为函数较长,须要分段解释一下。

// 数据结构定义

struct AndroidLogFormat_t {

android_LogPriority global_pri;

FilterInfo *filters;

AndroidLogPrintFormat format;

};

typedef struct AndroidLogFormat_t AndroidLogFormat;

// 变量声明

static AndroidLogFormat *g_logformat;

// 函数定义

AndroidlogFormat *android_log_format_new()

{

AndroidLogFormat *p_ret;

p_ret = calloc(1, sizeof(AndroidLogFormat));

p_ret->global_pri = ANDROID_LOG_VERBOSE;

p_ret->format = FORMAT_BRIEF;

return p_ret;

}

// main函数

int main(int argc, char **argv)

{

int err = 0;

int hasSetLogFormat = 0;

int clearLog = 0;

int getLogSize = 0;

int mode = O_RDONLY;

const char *forceFilters = NULL;

log_device_t *devices = NULL;

log_device_t *dev;

bool needBinary = false;

g_logformat = android_log_format_new();

}

从函数android_log_format_new的实现能够看出,全局变量g_logformat指定了日志的默认输出格式为FOTAMT_BRIEF,指定了日志记录过滤优先级为ANDROID_LOG_VERBOSE.

回到main函数。我们继续分析main函数对传入參数的解析过程源代码例如以下:

#define LOG_FILE_DIR "/dev/log/"

for (;;) {

int ret;

ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B");

if (ret < 0) break;

switch(ret) {

case 'd':

// logcat日志驱动程序中没有日志记录可读时,logcat工具直接退出

g_nonblock = true;

break;

case 't':

// 同d选项。而且指定每次日志输出的条数为g_tail_lines

g_nonblock = true;

g_tail_lines = atoi(optarg);

break;

case 'b': {

// 通过-b參数指定须要打开的日志文件(main,system,radio,events)。并将其设备数据结构加入到链表devices中

char* buf = (char*) malloc(strlen(LOG_FILE_DIR) + strlen(optarg) + 1);

strcpy(buf, LOG_FILE_DIR);

strcat(buf, optarg);

bool binary = strcmp(optarg, "events") == 0;

if (binary) {

needBinary = true;

}

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]);

}

android::g_devCount ++;

}

break;

case 'B':

// 表示以二进制格式输出日志

android::g_printBinary = 1;

break;

case 'f':

// 日志记录输出文件的名称

android::g_outputFileName = optarg;

break;

case 'r':

// 记录每个日志记录输出文件的最大容量

if (optarg == NULL) {

android::g_logRotateSizeKBytes = DEFAULT_LOG_ROTATE_SIZE_KBYTES;

} else {

long logRotateSize;

char *lastDigit;

if (!isdigit(optarg[0])) {

fprintf(stderr, "Invalid parameter to -r\n");

exit(-1);

}

android::g_logRotateSizeKBytes = atoi(optarg);

}

break;

case 'n':

if (!isdigit(optarg[0])) {

// 这里有个Android源代码的Bug,源代码里写的是-r(太粗心了吧!

)

fprintf(stderr, "Invalid parameter to -n\n");

exit(-1);

}

android:g_maxRotatedLogs = atoi(optarg);

break;

case 'v':

// 设置日志输出格式

err = setLogFormat(optarg);

if (err < 0) {

fprintf(stderr, "Invalid parameter to -v\n");

exit(-1);

}

hasSetLogFormat = 1;

break;

}

这段代码主要是解析參数。每个參数的含义我已经通过凝视写到代码里去了。

解析完命令行參数后,代码继续往后执行:

if (!devices) {

devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');

android::g_devCount = 1;

int accessmode = (mode & O_RDONLY) ?

R_OK : 0 | (mode & O_WRONLY) ? W_OK : 0;

// 假设/dev/log/system文件存在。默认也读取system日志

if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {

devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');

android::g_devCount ++;

}

}

这段代码的主要作用是:当用户没有指定-b參数时,默认将main和system的log输出到logcat中。

接下来,继续分析main函数源代码。

android:setupOutput();

static void setupOutput()

{

if (g_outputFileName == NULL) {

// logcat的默认输出为标准输出

g_outFD = STDOUT_FILENO;

} else {

// 使用-f选项指定了输出

struct stat statbuf;

g_outFD = openLogFile(g_outputFileName);

if (g_outFD < 0) {

perror("Couldn't open output file");

exit(-1);

}

fstat(g_outFD, &statbuf);

g_outByteCount = statbuf.st_size;

}

}

回到main函数,继续向下阅读源代码。

if (hasSetLogFormat == 0) {

const char* logFormat = getenv("ANDROID_PRINTF_LOG");

if (logFormat != NULL) {

err = setLogFormat(logFormat);

if (err < 0) {

fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", logFormat);

}

}

}

这块代码的主要作用是:当用户没有指定特定的输出格式时,logcat会查一下ANDROID_PRINTF_LOG的值,假设这个值设置的话,就将日志格式改为这个值。

设置好logcat日志输出格式后,logcat会继续向下执行。

if (forceFilters) {

// 不须要管这里

} else if (argc == optind){

// 不须要care

} else {

// 添加logcat过滤器

for (int i = optind; i < argc; i ++) {

err = android_log_addFilterString(g_logformat, argv[i]);

if (err < 0) {

fprintf(stderr, "Invalid filter expression '%s'\n", argv[i]);

exit(-1);

}

}

}

int android_log_addFilterString(AndroidLogFormat *p_format, const char *filterString)

{

char *filterStringCopy = strdup(filterString);

char *p_cur = filterStringCopy;

char *p_ret;

int err;

while (NULL != (pret = strsep(&p_cur, " \t,"))) {

if (p_ret[0] != '\0') {

err = android_log_addFilterRule(p_format, p_ret);

if (err < 0) {

goto err;

}

}

}

free(filterStringCopy);

return 0;

error:

free(filterStringCopy);

return -1;

}

int android_log_addFilterRule(AndroidLogFormat *p_format, const char *filterExpression) {

size_t i = 0;

size_t tagNameLength;

android_LogPriority pri = ANDROID_LOG_DEFAULT;

// 获取tag的长度

tagNameLength = strcspn(filterExpression, ":");

if (tagNameLength == 0) {

goto err;

}

// 获取tag相应的日志权限pri

if (filterExpression[tagNameLength] == ':') {

pri = filterCharToPri(filterExpresion[tagNameLength + 1]);

if (pri == ANDROID_LOG_UNKNOWN) {

goto err;

}

}

if (0 == strncmp("*", filterExpression, tagNameLength)) {

// *默认是打印当前tag的全部级别的log

if (pri == ANDROID_LOG_DEFAULT) {

pri = ANDROID_LOG_DEBUG;

}

p_format->global_pri = pri;

} else {

if (pri == ANDROID_LOG_DEFAULT) {

pri = ANDROID_LOG_VERBOSE;

}

char *tagName;

tagName = strdup(filterExpression);

tagName[tagNameLength] = '\0';

FilterInfo *p_fi = filterinfo_new(tagName, pri);

free(tagName);

// 头插法将过滤条件插入

p_fi->p_next = p_format->filters;

p_format->filters = p_fi;

}

return 0;

error:

return -1;

}

logcat日志过滤格式为:[:priority]。

当中,tag为随意字符串,代表一个日志记录标签。

priority是一个字符,表示一个日志记录优先级。添加了过滤后。也是代表日志记录tag-过滤tag时,仅仅有priority大于过滤priority的日志才会被输出。

日志记录的读取过程

Logcat工具是从源代码文件logcat.cpp中的函数readLogLines開始读取日志记录的。我们来分段阅读这个函数的实现。

android::readLogLines(devices);

函数实现例如以下:

static void readLogLines(log_device_t* devices)

{

log_device_t* dev;

int max = 0;

int ret;

int queued_lines = 0;

bool sleep = false;

int result;

fd_set readset;

for (dev = devices; dev; dev = dev->next) {

if (dev->fd > max) {

max = dev->fd;

}

}

while (1) {

do {

timeval timeout = {0, 5000};

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);

}

}

因为Logcat工具有可能打开了多个日志设备,因此,while循环中使用了select函数来同一时候监控他们是否有内容可读,即是否有新日志须要读取。

调用select函数时,须要设定用来查找这些打开的日志设备中的最大文件描写叙述符,并保存在变量max中。

当代码跳出select时,是存在两种可能性的。

当前logcat有新日志可读。

select选择超时,当前无新日志可读。

首先分析当前日志设备有新的日志记录可读的情况,例如以下所看到的:

if (result >= 0) {

for (dev = devices; dev; dev = dev->next) {

if (FD_ISSET(dev->fd, &readset)) {

queued_entry_t* entry = new queued_entry_t();

ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);

if (ret < 0) {

exit(EXIT_FAILURE);

} else if (!ret) {

exit(EXIT_FAILURE);

} else if (entry->entry.len != ret - sizeof(struct logger_entry)) {

exit(EXIT_FAILURE);

}

entry->entry.msg[entry->entry.len] = '\0';

dev->enqueue(entry);

++queued_lines;

}

}

}

每当设备有新数据可读时,就取出新数据构造queued_entry_t结构体,并插入到队列entry中,而且queued_lines全局变量+1。

if (result == 0) {

sleep = true;

while (true) {

chooseFirst(devices. &dev);

if (dev == NULL) {

break;

}

if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {

printNextEntry(dev);

} else {

skipNextEntry(dev);

}

-- queued_lines;

}

if (g_nonblock) {

return;

}

} else {

sleep = false;

while (g_tail_lines == 0 || queued_lines > g_tail_lines) {

chooseFirst(devices, &dev);

if (dev == NULL || dev->queue->next == NULL) {

if (entry_too_match) {

trigger_log(dev);

} else {

break;

}

}

if (g_tail_lines == 0) {

printnextEntry(dev);

} else {

skipNextEntry(dev);

}

--queued_lines;

}

}

因为Logcat工具是依照写入时间的先后顺序来输出日志记录的。因此,在输出已经读取的日志记录之前,Logcat工具会首先调用chooseFirst找到包括有最早的未输出日志记录的日志设备,源代码实现例如以下:

static void chooseFirst(log_device_t* dev, log_device_t** firstdev)

{

if (*firstdev = NULL; dev != NULL; dev = dev->next) {

if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {

*firstdev = dev;

}

}

}

chooseFirst函数仅仅是用来找到最早的日志记录。而日志真正的输出是通过函数printNextEntry实现的。源代码实现例如以下:

static void printNextEntry(log_device_t* dev)

{

maybePrintStart(dev);

processBuffer(dev, &dev->queue->entry);

skipNextEntry(dev);

}

当中,maybePrintStart是用例推断当前日志设备dev中的日志记录是否是第一次输出,假设是第一次输出,会添加一些特定标志信息。

static void maybePrintStart(log_device_t* dev)

{

if (!dev->printed) {

dev->printed = true;

if (g_devCount > 1 && !g_printBinary) {

char buf[1024];

snprintf(buf, sizeof(buf), "--------- beginning of %s\n", dev->device);

if (write(g_outFD, buf, strlen(buf)) < 0) {

exit(-1);

}

}

}

}

日志输出后,就须要将日志从队列中删除,这是通过调用函数skipNextEntry实现的。源代码例如以下:

static void skipNextEntry(log_device_t* dev)

{

maybePrintStart(dev);

queued_entry_t* entry = dev->queue;

dev->queue = entry->next;

delete entry;

entry_num --;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值