- 日志模块的C语言实现
-
模块接口
模块接口比较简单,因为主要只有写日志的操作。
创建日志模块变量
[cpp]
log_t log_new(log_type_t type,const char *file, const char *facility);
log_type_t用于控制日志的类型,可以标识为系统日志,文件以及标准输出,是一个枚举类型,定义如下:
[cpp]
typedef enum {
log_STDOUT,
log_SYSLOG,
log_FILE
} log_type_t;
file:这个字段有两种意思,如果日志类型为文件时,该字段表示文件名。如果日志类型为系统日志,该字段表示ident值。
facility:这个参数用于指明记录日志的程序的类型,我们传递的格式都是系统保留的,并且以这种形式传递local3。
写日志
[cpp]
void log_write(log_t log, int level, const char *msgfmt, ...);
level表示写入日志的级别,如:通告,错误,警告之类的,可以按照通用的错误类型划分。分别定义字符串如下:
[html]
static const char *_log_level[] =
{
"emergency",
"alert",
"critical",
"error",
"warning",
"notice",
"info",
"debug"
};
释放日志模块变量
[cpp]
void log_free(log_t log);
二,数据结构
定义数据结构如下:
[cpp]
typedef struct log_st
{
log_type_t type;
FILE *file;
} *log_t;
只有日志类型和一个文件结构指针。
下面说一下记录日志的程序类型,主要有以下几种日志程序的类型:
[html]
LOG_AUTH :安全/授权消息
LOG_AUTHPRIV:安全/授权消息
LOG_CRON:时间守护进程(cron和at)专用
LOG_DAEMON:其它系统守护进程
LOG_KERN:核心消息
LOG_LOCAL0到LOG_LOCAL7:系统保留
LOG_LPR:printer子系统
LOG_MAIL:mail子系统
LOG_NEWS:USENET新闻子系统
LOG_SYSLOG:syslogd进程内部所产生的消息
LOG_USER(缺省):一般使用者缺省使用消息
LOG_UUCP:UUCP子系统
LOG_FTP:FTP子系统使用
我们在这里用的是系统保留,也就是只有LOG_LOCAL0到LOG_LOCAL7,那就要求必须重新封装,以做到以下两点:
如果传递的facility不属于local0~local7,就默认local0~local7中的一个。
需要将传递的local0转换成LOG_LOCAL0,以此类推。
这里为什么传递local0,而不直接传递LOG_LOCAL0?直接传递也是可以的,只所以传递字符串,是因为大多数的日志都是在配置文件中,而配置文件以字符串的形式存取。而LOG_LOCAL0,并不是一个固定的整数值。
[cpp]
typedef struct log_facility_st
{
const char *facility;
int number;
} log_facility_t;
static log_facility_t _log_facilities[] = {
{ "local0", LOG_LOCAL0 },
{ "local1", LOG_LOCAL1 },
{ "local2", LOG_LOCAL2 },
{ "local3", LOG_LOCAL3 },
{ "local4", LOG_LOCAL4 },
{ "local5", LOG_LOCAL5 },
{ "local6", LOG_LOCAL6 },
{ "local7", LOG_LOCAL7 },
{ NULL, -1 }
};
这是定义的一个常量数组,分别和系统日志设备对映起来。在操作的日志文件中,可以看到和这相关的配置,位于/etc/syslog.conf文件中,如:
[cpp]
# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.* /dev/console
# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
*.info;mail.none;authpriv.none;cron.none /var/log/messages
# The authpriv file has restricted access.
authpriv.* /var/log/secure
# Log all the mail messages in one place.
mail.* -/var/log/maillog
# Log cron stuff
cron.* /var/log/cron
# Everybody gets emergency messages
*.emerg *
# Save news errors of level crit and higher in a special file.
uucp,news.crit /var/log/spooler
# Save boot messages also to boot.log
local7.* /var/log/boot.log
local5.* /var/log/client.log
这上面就配置了local7和local5这两个本地日志设备,其实就是两个日志文件。其实在系统的头文件中就是<sys/syslog.h>中,也定义了这样的一个数组:
[cpp]
#ifdef SYSLOG_NAMES
CODE facilitynames[] =
{
{ "auth", LOG_AUTH },
{ "authpriv", LOG_AUTHPRIV },
{ "cron", LOG_CRON },
{ "daemon", LOG_DAEMON },
{ "ftp", LOG_FTP },
{ "kern", LOG_KERN },
{ "lpr", LOG_LPR },
{ "mail", LOG_MAIL },
{ "mark", INTERNAL_MARK }, /* INTERNAL */
{ "news", LOG_NEWS },
{ "security", LOG_AUTH }, /* DEPRECATED */
{ "syslog", LOG_SYSLOG },
{ "user", LOG_USER },
{ "uucp", LOG_UUCP },
{ "local0", LOG_LOCAL0 },
{ "local1", LOG_LOCAL1 },
{ "local2", LOG_LOCAL2 },
{ "local3", LOG_LOCAL3 },
{ "local4", LOG_LOCAL4 },
{ "local5", LOG_LOCAL5 },
{ "local6", LOG_LOCAL6 },
{ "local7", LOG_LOCAL7 },
{ NULL, -1 }
};
#endif
前面说为了便宜传递参数,我们假设我们传递的系统日志设备为local5这种形式,但要求是LOG_LOCAL5这个宏,因此我们根据上面的数组,作个转换:
[cpp]
static int _log_facility(const char *facility) {
log_facility_t *lp;
if (facility == NULL) {
return -1;
}
for (lp = _log_facilities; lp->facility; lp++) {
if (!strcasecmp(lp->facility, facility)) {
break;
}
}
return lp->number;
}
这个函数会根据local5,找到并返回LOG_LOCAL5。
三,创建日志模块
代码如下:
[cpp]
log_t log_new(log_type_t type,const char *ident, const char *facility)
{
log_t log;
int fnum = 0;
log = (log_t) calloc(1, sizeof(struct log_st));
log->type = type;
if(type == log_SYSLOG) { // 系统日志
fnum = _log_facility(facility);
if (fnum < 0)
fnum = LOG_LOCAL7;
openlog(ident, LOG_PID, fnum); // 下面会对openlog系统调用进行说明。
return log;
}
else if(type == log_STDOUT) { // 标准输出。
log->file = stdout;
return log;
}
log->file = fopen(ident, "a+");
if(log->file == NULL) // 文件,如果打开文件失败,会将其定义为标准输出。
{
log->type = log_STDOUT;
log->file = stdout;
}
return log;
}
四,关于openlog调用
在写系统日志时,不是必须要调用openlog,如果没有调用openlog,那么在第一次调用syslog时,会自动调用openlog,此函数原型如下:
[cpp]
void openlog(const char *ident, int option, int facility);
这个函数用来打开一个到系统日志记录程序的连接,打开之后就可以用syslog或vsyslog函数向系统日志里记录日志。
ident:这个参数是一个标记,在写入日志时,每行都会在前面自动的加上这个标记,通常可以写成当前程序的名称或者是同一程序不同的端口调用。
option:该参数可以取值LOG_CONS, LOG_NDELAY, LOG_NOWAIT, LOG_ODELAY, LOG_PERROR, LOG_PID。都表示不同的意思,我们这里取LOG_PID表示每一行日志都包含当前程序的进程ID号。
facility:表示往哪个日志里写,其实也是表示由哪个具体的系统日志类型来记录日志。
五,写日志
在调用log_new之后,此时log_st有两种可能:
类型为系统日志类型,那么已经打开了系统日志。接下来只要直接调用syslog函数就OK了。
日志类型不是系统日志,而是文件或者标准输出,此时log_st结构体的域FILE *file,已经指向了具体的文件,后面只要调用fwrite类函数就可以了。
代码如下:
[cpp]
#define MAX_LOG_LINE (1024)
void log_write(log_t log, int level, const char *msgfmt, ...)
{
va_list ap;
char *pos, message[MAX_LOG_LINE+1];
int sz, len;
time_t t;
if(log->type == log_SYSLOG) { // 写入系统日志
va_start(ap, msgfmt);
len = vsnprintf(message, MAX_LOG_LINE, msgfmt, ap);
if (len > MAX_LOG_LINE)
message[MAX_LOG_LINE] = '\0';
else
message[len] = '\0';
syslog(level, "%s", message); // 下面会说明syslog调用。
va_end(ap);
return;
}
t = time(NULL); // 时间戳
pos = ctime(&t);
sz = strlen(pos);
pos[sz-1]=' ';
len = snprintf(message, MAX_LOG_LINE, "%s[%s] ", pos, _log_level[level]);
if (len > MAX_LOG_LINE)
message[MAX_LOG_LINE] = '\0';
else
message[len] = '\0';
for (pos = message; *pos != '\0'; pos++); /*empty statement */
sz = pos - message;
va_start(ap, msgfmt);
vsnprintf(pos, MAX_LOG_LINE - sz, msgfmt, ap);
va_end(ap); // 根据传入参数,组织文本信息
fprintf(log->file,"%s", message); // 写入文件。
fprintf(log->file, "\n");
fflush(log->file);
}
六,syslog调用
函数的声明如下:
[cpp]
void syslog(int priority, const char * message, ...);
priority:消息的紧急级别。
message:第二个参数是消息及其格式,之后是格式对应的参数,如同C语言里面printf输出函数一样使用。
第一个参数priority,它是由severity level和facility组成的,Facility已经在上面介绍了,下面介绍一下severity level,也就是消息的重要级别,它主要包括:
[html]
LOG_EMERG:紧急状况
LOG_ALERT:高优先级问题,比如说 数据库崩溃等,必须要立即采取反应行动
LOG_CRIT:重要状况发生,比如硬件故障
LOG_ERR:错误发生
LOG_WARNING:警告发生
LOG_NOTICE:一般状况,需要引起注意
LOG_INFO:信息状况
LOG_DEBUG:调试消息
七,释放
这个操作就很简单了,
[cpp]
void log_free(log_t log) {
if(log->type == log_SYSLOG)
closelog();
else if(log->type == log_FILE)
fclose(log->file);
free(log);
}
八,使用的例子
这个使用也是很简单的,分成三部:创建,写入,释放
[cpp]
log_t log = log_new(log_SYSLOG, "Example", "local5"); // 创建日志模块变量。
log_write(log, LOG_NOTICE, "应用程序正在启动中");
log_free(log);