上接:如何在嵌入式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 <syslog.h> |
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
<script src="/72890.js" language="JavaScript1.1" type="text/javascript"> </script>
//将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