Android logcat 添加kernel log 并保存到本地

时间:2024-05-21 17:57:34

Android logcat 添加kernel log 并保存到本地

 

概述:作为一个Android系统工程师,最头疼的莫过于死机和重启,这种概率性Bug,往往在办公室的测试环境下很难复现,但是在实际使用中,确实会出现死机和重启,这时候有一份可靠的log,是尤其重要的,那么除了分析Android系统log,如果还能有一份kernel log是不是就更容易定位问题呢?

 

我们最常用的平台,有高通,MTK,RK,……,除了MTK有一个mtklog,其他的平台好像都没有内置这种打印log的应用,所以有时候就需要自己动手丰衣足食。

 

思路1.将MTK logger移植过去,然后开始了代码阅读,结果发现里面涉及的东西太多,逻辑还挺复杂,没有10天半个月,我很难搞清楚,故放弃(也怪自己太懒,抱拳),如果有哪位同学比较精通这个,希望能分享一下,多谢!!

 

思路2.logcat添加个参数,去打印kernel log,然后开机自启一个进程,去打印log并保存本地,貌似这个方案更简单一点,说干就干,开始实现功能。

 

Android源码/system/core/logcat/

预览一下目录:

Android logcat 添加kernel log 并保存到本地

相对来说工程比较小了,经过一番百度,我发现使用klogctl来保存kernel log是比较靠谱的,那么写一个函数:

static void processKernel(int op)

{

char *buffer;

char *p;

int n, klog_buf_len;

//新建一个文件

const char *fileName = getLogFileNeme(0);

 

FILE *fp = fopen(fileName, "wa");

if (fp == NULL)

{

fprintf(stderr, "BorrieGuo processKernel Eric Open File Failed!!!\n");

return;

}

//获取kernel log缓存大小

klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);

 

if (klog_buf_len <= 0)

{

klog_buf_len = FALLBACK_KLOG_BUF_LEN;

}

//申请一个buffer

buffer = (char *)malloc(klog_buf_len + 1);

 

if (!buffer)

{

fprintf(stderr, "BorrieGuo processKernel malloc ERROR\n");

return;

}

 

p = buffer;

//设置kernel log模式

if (op == KLOG_READ_CLEAR)

{

//获取全部log后并清除缓存

op = KLOG_READ_CLEAR;

}

else

{

//获取全部log

op = KLOG_READ_ALL;

}

 

//获取log

n = klogctl(op, buffer, klog_buf_len);

if (n <= 0)

{

fprintf(stderr, "BorrieGuo processKernel klogctl ERROR\n");

if (!remove(fileName))

{

fprintf(stderr, "BorrieGuo delete %s success\n", fileName);

 

}

else

{

fprintf(stderr, "BorrieGuo delete %s failed\n", fileName);

 

}

return;

}

else

{

fprintf(stderr, "BorrieGuo processKernel n = %d,op = %d\n", n, op);

}

 

buffer[n] = '\0';

//将log写入文件

fwrite(p, n, 1, fp);

fflush(fp);

fclose(fp);

}

 

上述方法验证成功后,我们直接添加进去就可以了,那么我们就直接拿logcat.cpp开刀吧,Android源码/system/core/logcat/logcat.cpp,然后我们来看一下logcat的main函数,添加一个新参数 -a 表示all,主要逻辑是保存kernel log和系统log到本地,并且以开始时间命名文件,当logcat打印一段时间后(我们设置的时间是30分钟存一个文件),再创建一对文件保存

代码如下:

 

int main(int argc, char **argv)

{

此处代码省略……

 

if (argc == 2 && 0 == strcmp(argv[1], "--help"))

{

show_help(argv[0]);

return EXIT_SUCCESS;

}

 

//BorrieGuo add

bool hasOpenKernelLog = false;

//add end

for (;;)

{

int ret;

 

int option_index = 0;

// list of long-argument only strings for later comparison

static const char pid_str[] = "pid";

static const char wrap_str[] = "wrap";

static const char print_str[] = "print";

static const struct option long_options[] = {

//BorrieGuo 添加-a参数

{"all", no_argument, NULL, 'a'},

{"binary", no_argument, NULL, 'B'},

{"buffer", required_argument, NULL, 'b'},

{"buffer-size", optional_argument, NULL, 'g'},

{"clear", no_argument, NULL, 'c'},

{"dividers", no_argument, NULL, 'D'},

{"file", required_argument, NULL, 'f'},

{"format", required_argument, NULL, 'v'},

// hidden and undocumented reserved alias for --regex

{"grep", required_argument, NULL, 'e'},

// hidden and undocumented reserved alias for --max-count

{"head", required_argument, NULL, 'm'},

{"last", no_argument, NULL, 'L'},

{"max-count", required_argument, NULL, 'm'},

{pid_str, required_argument, NULL, 0},

{print_str, no_argument, NULL, 0},

{"prune", optional_argument, NULL, 'p'},

{"regex", required_argument, NULL, 'e'},

{"rotate-count", required_argument, NULL, 'n'},

{"rotate-kbytes", required_argument, NULL, 'r'},

{"statistics", no_argument, NULL, 'S'},

// hidden and undocumented reserved alias for -t

{"tail", required_argument, NULL, 't'},

// support, but ignore and do not document, the optional argument

{wrap_str, optional_argument, NULL, 0},

{NULL, 0, NULL, 0}};

 

 

//此处也需要修改

ret = getopt_long(argc, argv, ":acdDLt:T:gG:sQf:r:n:v:b:BSpP:m:e:",

long_options, &option_index);

 

if (ret < 0)

{

break;

}

printf("BorrieGuo getopt_long: %d\n", ret);

switch (ret)

{

case 0:

// One of the long options

if (long_options[option_index].name == pid_str)

{

// ToDo: determine runtime PID_MAX?

if (!getSizeTArg(optarg, &pid, 1))

{

logcat_panic(true, "%s %s out of range\n",

long_options[option_index].name, optarg);

}

break;

}

if (long_options[option_index].name == wrap_str)

{

mode |= ANDROID_LOG_WRAP |

ANDROID_LOG_RDONLY |

ANDROID_LOG_NONBLOCK;

// ToDo: implement API that supports setting a wrap timeout

size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;

if (optarg && !getSizeTArg(optarg, &dummy, 1))

{

logcat_panic(true, "%s %s out of range\n",

long_options[option_index].name, optarg);

}

if (dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT)

{

fprintf(stderr,

"WARNING: %s %u seconds, ignoring %zu\n",

long_options[option_index].name,

ANDROID_LOG_WRAP_DEFAULT_TIMEOUT, dummy);

}

break;

}

if (long_options[option_index].name == print_str)

{

g_printItAnyways = true;

break;

}

break;

 

//添加的命令a

case 'a':

// BorrieGuo add printf kernel log

hasOpenKernelLog = true;

break;

 

case 's':

// default to all silent

android_log_addFilterRule(g_logformat, "*:s");

break;

 

此处代码省略……

if (g_printItAnyways && (!g_regex || !g_maxCount))

{

// One day it would be nice if --print -v color and --regex <expr>

// could play with each other and show regex highlighted content.

fprintf(stderr, "WARNING: "

"--print ignored, to be used in combination with\n"

" "

"--regex <expr> and --max-count <N>\n");

g_printItAnyways = false;

}

 

//BorrieGuo add this do while

do

{

if (!devices)

{

dev = devices = new log_device_t("main", false);

g_devCount = 1;

if (android_name_to_log_id("system") == LOG_ID_SYSTEM)

{

dev = dev->next = new log_device_t("system", false);

g_devCount++;

}

if (android_name_to_log_id("crash") == LOG_ID_CRASH)

{

dev = dev->next = new log_device_t("crash", false);

g_devCount++;

}

}

 

//BorrieGuo add判断是否需要打印kernel log

if (hasOpenKernelLog)

{

//get kernel log

if (currentTime == 0)

{

//get kernel log frist

processKernel(KLOG_READ_ALL);

}

else

{

processKernel(KLOG_READ_CLEAR);

}

//set new file获取 Android 系统log,所保存的文件名

const char *mainName = getLogFileNeme(1);

// redirect output to a file

g_outputFileName = mainName;

}

 

if (g_logRotateSizeKBytes != 0 && g_outputFileName == NULL)

{

logcat_panic(true, "-r requires -f as well\n");

}

 

//设置输出Android系统log

setupOutput();

 

if (hasSetLogFormat == 0)

{

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

 

此处省略……

 

dev = NULL;

log_device_t unexpected("unexpected", false);

 

fprintf(stderr, "BorrieGuo g_maxCount : %zu,g_printCount : %zu\n", g_maxCount, g_printCount);

循环打印Android系统log

while (!g_maxCount || (g_printCount < g_maxCount))

{

struct log_msg log_msg;

log_device_t *d;

int ret = android_logger_list_read(logger_list, &log_msg);

 

if (ret == 0)

{

logcat_panic(false, "read: unexpected EOF!\n");

}

 

if (ret < 0)

{

if (ret == -EAGAIN)

{

break;

}

 

if (ret == -EIO)

{

logcat_panic(false, "read: unexpected EOF!\n");

}

if (ret == -EINVAL)

{

logcat_panic(false, "read: unexpected length.\n");

}

logcat_panic(false, "logcat read failure");

}

 

for (d = devices; d; d = d->next)

{

if (android_name_to_log_id(d->device) == log_msg.id())

{

break;

}

}

if (!d)

{

g_devCount = 2; // set to Multiple

d = &unexpected;

d->binary = log_msg.id() == LOG_ID_EVENTS;

}

 

if (dev != d)

{

dev = d;

maybePrintStart(dev, printDividers);

}

if (g_printBinary)

{

printBinary(&log_msg);

}

else

{

processBuffer(dev, &log_msg);

}

 

//BorrieGuo add

if (hasOpenKernelLog)

{

//超时跳出循环,重新打印log

if (processTimer(g_outputFileName))

{

break;

}

}

//add end

}

} while (hasOpenKernelLog);

 

android_logger_list_free(logger_list);

fprintf(stderr, "BorrieGuo logcat EXIT_SUCCESS\n");

return EXIT_SUCCESS;

}

 

经过上述修改,然后我们使用logcat -a 就可以保存log至本地了,类似logcat -f /文件路径/

添加自定义命令成功后,我们就考虑,怎么开机自启,所以我就在init.rc添加了service,用系统属性值做开关,代码如下:

 

#Add for log

service logcat-all /system/bin/logcat -a //使用logcat -a

user root

disabled

console

service logcat-all-w /system/bin/logcat *:w -a //使用logcat *:w -a

user root

disabled

console

service logcat-all-e /system/bin/logcat *:e -a //使用logcat *:e -a

user root

disabled

console

on property:persist.sys.***log.enable=1

start logcat-all-e //只保存error级别的log

on property:persist.sys.***log.enable=2

start logcat-all-w //保存 error级别和 warning级别的log

on property:persist.sys.***log.enable=3

start logcat-all //保存全部log

on property:persist.***log.enable=0

stop logcat-all

stop logcat-all-e

stop logcat-all-w

 

修改完成!!!

上述修改成功后,我们是搭配一个文件管理APP(自定义的)对文件进行,删除,上传等操作(APP比较简单,我就不再多做解释了),这样我们获取log就轻松了很多,这种思路,修改的地方虽然不是很多,但是个人感觉不是很好,最好还是研究一下MTK的logger,然后移植出来比较好,如果大家有好的办法和思路,希望多多批评!!!