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/
预览一下目录:
相对来说工程比较小了,经过一番百度,我发现使用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,然后移植出来比较好,如果大家有好的办法和思路,希望多多批评!!!