上接:如何在嵌入式Linux产品中做立体、覆盖产品生命期的调试 ( 2)
这篇谈谈log的做法:
上面一篇谈了print的用法,一般print是把结果输出到stdout/stderr上面去了,也就是我们常见的terminal上面去了;这有个弊端,就是我们的程序是在debug状态时,我们才能看到这些调试信息;
如果程序不在debug状态下运行,这些打印信息是看不着的,而且程序一直在debug状态运行,也不能反映实际情况,尤其对于嵌入式产品来讲;
不在debug状态运行时的一些异常,如何能看到呢?这就用到了Log了。所谓的log就是把运行信息写到文件中去,保存下来;
Linux 有完善的Log系统,比如core的产生,就是Log的一种形式,只不过这时的Log是记录程序“病入膏肓”的状态,我这里所说的Log一般是记录程序中可以预见的一些异常信息,供事后分析使用;
我在这里提供两种Log供大家使用,一种是利用Linux syslog, 一种是我们自己写log;并且把两种log结合到一块,融合到上一篇博文中,结合Log and print:
使用Linux syslog的步骤:
1 头文件
#include
2 几个接口函数
openlog ( … );
vsyslog (…)
syslog (....)
Linux syslog一般会把我们的信息写到/etc/log/messages中,当然你也可以配置syslog.conf文件去修改保存的路径、文件名等,至于如何修改,参考附录1. 这里就不冲淡我们的主题了:)
一个问题:保存到/etc/log/messages中好不好?
不好,为什么呢?一个产品,尤其手机中跑的程序至少有30多个,甚至更多,如果每个程序都把log信息写到/etc/log/mssages中,而且和系统的log信息混到了一块;这样的Log会很难看,不同程序的调试信息混到一块,事后查找非常麻烦! 吃大锅饭,责任不明呀,呵呵。
怎么办?分灶吃!
Linux syslog有优点,但是缺点更突出!我们只要利用syslog的思想就行了:记录异常、重要的信息到文件中;
好,现在我们准备自己写异常信息到文件中,我们想给不同的程序维护一个自己的log日志,这很好办:给不同的文件名就行了;
另外一个值得考虑的问题:空间的问题!嵌入式产品的Flash空间很宝贵,不能滥用!如果一个程序的异常问题很多,记录的日志很多,日积月累,可能会把Flash的空间给吃完,这样会导致严重的问题,所有的程序都不能正常跑了!所以要限制你的log文件大小,比如说1M大小,看你的产品的Flash大小了;
有了上面的准备,下面我们开始做syslog和自己的log的结合体:
1 准备
static FILE *logfile;
static FILE syslog_dummy;
static int loglevel;
static gboolean first_opened = FALSE;
#define LOG_FILE_MAX_SIZE (1024*1024) /* 1M */
static int userlog2syslog[] = {
[USER_LOG_DEBUG] = LOG_DEBUG,
[USER_LOG_INFO] = LOG_INFO,
[USER_LOG_NOTICE] = LOG_NOTICE,
[USER_LOG_ERR] = LOG_ERR,
[USER_LOG_CRIT] = LOG_CRIT,
};
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
static inline int user2syslog_level(int level)
{
if (level >= ARRAY_SIZE(userlog2syslog))
return LOG_ERR;
return userlog2syslog[level];
}
static inline char* get_syslog_level(int level)
{
if (level >= ARRAY_SIZE(userlog2syslog))
return NULL;
switch(level)
{
case USER_LOG_DEBUG:
return "LOG_DEBUG";
break;
case USER_LOG_INFO:
return "LOG_INFO";
break;
case USER_LOG_NOTICE:
return "LOG_NOTICE";
break;
case USER_LOG_ERR:
return "LOG_ERR";
break;
case USER_LOG_CRIT:
return "LOG_CRIT";
break;
default:
return NULL;
break;
}
}
2 初始化我们的Log
int userlog_init(const char *path)
{
if (path == NULL)
return -1;
gboolean b_exist = FALSE;
guint log_size = 0;
char *file_name;
file_name = malloc (strlen (path) + 1);
strcpy (file_name, path);
if (!strcmp(file_name, "syslog"))
{
logfile = &syslog_dummy;
openlog("Customized syslog: ", 0, LOG_DAEMON);
goto OUT;
}
else
{
struct stat buf;
if (stat (file_name, &buf) != 0)
b_exist = FALSE;
else
{
b_exist = TRUE;
log_size = buf.st_size;
}
}
free (file_name);
if (b_exist == TRUE)
{
//当一个log文件过大时,保存一个备份,然后重新开始记录;
//时间长了,你的Log目录下面可能有两个log: 比如my_log,
//my_log.old
//这样做的目的,是想尽可能的回溯信息;
if (log_size > LOG_FILE_MAX_SIZE)
{
char cmd[128] = {0};
sprintf(cmd, "cp -rf %s %s.old", path, path);
system(cmd);
memset(cmd, 0, sizeof(cmd));
sprintf(cmd, "rm -rf %s ", path);
system(cmd);
logfile = fopen(path, "a+");
}
else
logfile = fopen(path, "a+");
}
else
logfile = fopen(path, "a+");
OUT:
if (logfile == NULL)
return -1;
first_opened = TRUE;
user_log(USER_LOG_INFO, "logfile successfully opened.");
return 0;
}
3 写log
void user_log(int level, const char *file, int line, const char *function, const char *format, ...)
{
char *timestr;
va_list ap;
time_t tm;
FILE *outfd;
if (level < loglevel)
return;
// 把我们的信息写到syslog中,即/etc/log/messages中
if (logfile == &syslog_dummy)
{
va_start(ap, format);
vsyslog(user2syslog_level(level), format, ap);
va_end(ap);
}
else // 把我们的信息写到自己设置的一个文件中
{
if (logfile)
outfd = logfile;
else
outfd = stderr;
tm = time(NULL);
timestr = ctime(&tm);
timestr[strlen(timestr)-1] = '/0';
if (first_opened)
{
fprintf(outfd, "/n%s <%s> File:%s, #Line:%d, Func:%s(), Message:",
timestr, get_syslog_level(level), file, line, function);
first_opened = FALSE;
}
else
fprintf(outfd, "%s <%s> File:%s, #Line:%d, Func:%s(), Message:",
timestr, get_syslog_level(level), file, line, function);
va_start(ap, format);
vfprintf(outfd, format, ap);
va_end(ap);
fprintf(outfd, "/n");
fflush(outfd);
}
}
4 如何使用这样的Log
void test_func2(void)
{
if (exception) //有需要严重关注的异常,我就写Log
user_log(USER_LOG_INFO, "write the log to syslog or my own log file.");
return;
}
//一般在主程序中初始化一下我们的log
void main(int argc, char *argv[])
{
// 如果传入的参数是syslog,则我们的log将写入/etc/log/messages中
userlog_init("syslog");
// 如果传入的是你自己的log文件名,则写入你自己的Log中,
// 建议大家不要写到syslog中,吃大锅饭,到时候,所有的信息都搅在一块,难找!
// 还是包干到户的好!最好把你的Log放在程序的当前目录,随便你了。
// userlog_init("./my_log");
test_func2();
……
return;
}
如果,大家有其它的log方法,欢迎一块探讨。
附录1:
这是我以前在网上看到的一个如何配置syslog.conf方法,放在这里,供大家参考。
配置文件/etc/syslog.conf的实例解析
蓝森林 http://www.lslnet.com 2007年4月1日 18:37
//将info或更高级别的消息送到/var/log/messages,除了mail以外。
//其中*是通配符,代表任何设备;none表示不对任何级别的信息进行记录。
*.info;mail.none;authpriv.none /var/log/messages
//将authpirv设备的任何级别的信息记录到/var/log/secure文件中,这主要是一些和认、权限使用相关的信息。
authpriv.* /var/log/secure
//将mail设备中的任何级别的信息记录到/var/log/maillog文件中,这主要是和电子邮件相关的信息。
mail.* /var/log/maillog
//将cron设备中的任何级别的信息记录到/var/log/cron文件中,这主要是和系统中定期执行的任务相关的信息。
cron.* /var/log/cron
//将任何设备的emerg级别的信息发送给所有正在系统上的用户。
*.emerg *
//将uucp和news设备的crit级别的信息记录到/var/log/spooler文件中。
uucp,news.crit /var/log/spooler
//将和系统启动相关的信息记录到/var/log/boot.log文件中。
local7.* /var/log/boot.log