一,模块接口
模块接口比较简单,因为主要只有写日志的操作。
log_t log_new(log_type_t type,const char *file, const char *facility);log_type_t用于控制日志的类型,可以标识为系统日志,文件以及标准输出,是一个枚举类型,定义如下:
typedef enum { log_STDOUT, log_SYSLOG, log_FILE } log_type_t;file:这个字段有两种意思,如果日志类型为文件时,该字段表示文件名。如果日志类型为系统日志,该字段表示ident值。
void log_write(log_t log, int level, const char *msgfmt, ...);level表示写入日志的级别,如:通告,错误,警告之类的,可以按照通用的错误类型划分。分别定义字符串如下:
static const char *_log_level[] = { "emergency", "alert", "critical", "error", "warning", "notice", "info", "debug" };
void log_free(log_t log);
二,数据结构
定义数据结构如下:
typedef struct log_st { log_type_t type; FILE *file; } *log_t;只有日志类型和一个文件结构指针。
下面说一下记录日志的程序类型,主要有以下几种日志程序的类型:
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,那就要求必须重新封装,以做到以下两点:
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文件中,如:
# 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>中,也定义了这样的一个数组:
#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这个宏,因此我们根据上面的数组,作个转换:
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。
三,创建日志模块
代码如下:
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,那么在第一次调用syslog时,会自动调用openlog,此函数原型如下:
void openlog(const char *ident, int option, int facility);这个函数用来打开一个到系统日志记录程序的连接,打开之后就可以用syslog或vsyslog函数向系统日志里记录日志。
在调用log_new之后,此时log_st有两种可能:
#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调用
void syslog(int priority, const char * message, ...);
LOG_EMERG:紧急状况 LOG_ALERT:高优先级问题,比如说数据库崩溃等,必须要立即采取反应行动 LOG_CRIT:重要状况发生,比如硬件故障 LOG_ERR:错误发生 LOG_WARNING:警告发生 LOG_NOTICE:一般状况,需要引起注意 LOG_INFO:信息状况 LOG_DEBUG:调试消息
七,释放
这个操作就很简单了,
void log_free(log_t log) { if(log->type == log_SYSLOG) closelog(); else if(log->type == log_FILE) fclose(log->file); free(log); }八,使用的例子
这个使用也是很简单的,分成三部:创建,写入,释放
log_t log = log_new(log_SYSLOG, "Example", "local5"); // 创建日志模块变量。 log_write(log, LOG_NOTICE, "应用程序正在启动中"); log_free(log);