C 如何正确写入日志文件?
如何可靠地记录日志以确保多线程应用程序中条目的原子性?此外,我希望能够使用logrotate实用程序旋转日志 写入日志的最简单变体是next:C 如何正确写入日志文件?,c,linux,logging,C,Linux,Logging,如何可靠地记录日志以确保多线程应用程序中条目的原子性?此外,我希望能够使用logrotate实用程序旋转日志 写入日志的最简单变体是next: 打开/重新打开日志文件 通过printf()写入条目 在退出时关闭日志文件 以下是我的例子: // default log level static Cl_loglevl loglevel = LOGLEVEL_NONE; // log file descriptor (open with Cl_openlog) static FILE *logfd =
printf()写入条目
// default log level
static Cl_loglevl loglevel = LOGLEVEL_NONE;
// log file descriptor (open with Cl_openlog)
static FILE *logfd = NULL;
/**
* @brief Cl_openlog - open log file
* @param logfile - file name
* @return FILE struct or NULL if failed
*/
FILE *Cl_openlog(const char *logfile, Cl_loglevl loglvl){
if(logfd){
Cl_putlog(LOGLEVEL_ERROR, "Reopen log file\n");
fclose(logfd);
logfd = NULL;
char newname[PATH_MAX];
snprintf(newname, PATH_MAX, "%s.old", logfile);
if(rename(logfile, newname)) WARN("Can't rename old log file");
}
if(loglvl < LOGLEVEL_CNT) loglevel = loglvl;
if(!logfile) return NULL;
if(!(logfd = fopen(logfile, "w"))) WARN(_("Can't open log file"));
return logfd;
}
/**
* @brief Cl_putlog - put message to log file
* @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
* @param fmt - format and the rest part of message
* @return amount of symbols saved in file
*/
int Cl_putlog(Cl_loglevl lvl, const char *fmt, ...){
if(lvl > loglevel || !logfd) return 0;
char strtm[128];
time_t t = time(NULL);
struct tm *curtm = localtime(&t);
strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
int i = fprintf(logfd, "%s\t", strtm);
va_list ar;
va_start(ar, fmt);
i += vfprintf(logfd, fmt, ar);
va_end(ar);
fflush(logfd);
return i;
}
//默认日志级别
静态Cl_loglevl loglevel=loglevel_NONE;
//日志文件描述符(使用Cl_openlog打开)
静态文件*logfd=NULL;
/**
*@brief Cl_openlog-打开日志文件
*@param logfile-文件名
*@return FILE struct或NULL(如果失败)
*/
文件*Cl_openlog(const char*logfile,Cl_loglevl loglvl){
如果(logfd){
Cl_putlog(日志级别_错误,“重新打开日志文件”\n);
fclose(logfd);
logfd=NULL;
char newname[PATH_MAX];
snprintf(新名称,路径_MAX,“%s.old”,日志文件);
if(rename(logfile,newname))警告(“无法重命名旧日志文件”);
}
如果(loglvl日志级别,消息将不会打印)
*@param fmt-格式和消息的其余部分
*@返回文件中保存的符号数量
*/
内部日志(日志级别,常量字符*fmt,…){
如果(lvl>loglevel | | |!logfd)返回0;
char-strtm[128];
时间t=时间(空);
struct tm*curtm=本地时间(&t);
strftime(strtm,128,“%Y/%m/%d-%H:%m”,curtm);
int i=fprintf(logfd,“%s\t”,strtm);
va_列表ar;
va_启动(ar、fmt);
i+=vfprintf(logfd、fmt、ar);
va_端(ar);
fflush(logfd);
返回i;
}
调用Cl\u openlog
允许将日志旋转一次。我可以在SIG_USR1
处理程序中调用此函数,并通过logrotate发送此信号。但如何正确地写入文件以实现记录的原子性仍然不清楚
对于这样简单的问题,我不想使用log4c这样的外部库。您可以确保所有日志记录都是通过单个线程完成的,或者使用互斥体保护日志记录功能。
对于第一种情况,可以有一个工作线程在管道的读取端进行轮询;然后,每个线程将使用管道的写入端唤醒工作线程,并让它管理其日志(在堆的某个位置分配,并通过管道按地址传递)。
对于SIGUSR1处理程序(logrotate),也可以这样做 请注意,向管道()写入小于PIPE_BUF的数据保证不会被交错(因此它是原子的)。
因此,仅写入堆存储地址始终是原子的
最后但并非最不重要的一点是,您可以为每个线程使用不同的日志文件。嗯,我终于做到了:
// array with all opened logs - for error/warning messages
static Cl_log *errlogs = NULL;
static int errlogsnum = 0;
/**
* @brief Cl_createlog - create log file: init mutex, test file open ability
* @param log - log structure
* @return 0 if all OK
*/
int Cl_createlog(Cl_log *log){
if(!log || !log->logpath || log->loglevel <= LOGLEVEL_NONE || log->loglevel >= LOGLEVEL_CNT) return 1;
FILE *logfd = fopen(log->logpath, "a");
if(!logfd){
WARN("Can't open log file");
return 2;
}
fclose(logfd);
pthread_mutex_init(&log->mutex, NULL);
errlogs = realloc(errlogs, (++errlogsnum) *sizeof(Cl_log));
if(!errlogs) errlogsnum = 0;
else memcpy(&errlogs[errlogsnum-1], log, sizeof(Cl_log));
return 0;
}
/**
* @brief Cl_putlog - put message to log file with/without timestamp
* @param timest - ==1 to put timestamp
* @param log - pointer to log structure
* @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
* @param fmt - format and the rest part of message
* @return amount of symbols saved in file
*/
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevl lvl, const char *fmt, ...){
if(!log || !log->logpath || log->loglevel < 0 || log->loglevel >= LOGLEVEL_CNT) return 0;
if(lvl > log->loglevel) return 0;
if(pthread_mutex_lock(&log->mutex)) return 0;
int i = 0;
FILE *logfd = fopen(log->logpath, "a");
if(!logfd) goto rtn;
if(timest){
char strtm[128];
time_t t = time(NULL);
struct tm *curtm = localtime(&t);
strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
i = fprintf(logfd, "%s\t", strtm);
}
va_list ar;
va_start(ar, fmt);
i += vfprintf(logfd, fmt, ar);
va_end(ar);
fclose(logfd);
rtn:
pthread_mutex_unlock(&log->mutex);
return i;
}
//包含所有打开日志的数组-用于错误/警告消息
静态Cl_log*errlogs=NULL;
静态int errlogsnum=0;
/**
*@brief Cl_createlog-创建日志文件:初始化互斥,测试文件打开能力
*@param log-日志结构
*@如果一切正常,返回0
*/
int Cl_createlog(Cl_log*log){
如果(!log | |!log->logpath | | log->loglevel loglevel>=loglevel_CNT)返回1;
文件*logfd=fopen(log->logpath,“a”);
如果(!logfd){
警告(“无法打开日志文件”);
返回2;
}
fclose(logfd);
pthread_mutex_init(&log->mutex,NULL);
errlogs=realloc(errlogs,(++errlogsnum)*sizeof(Cl_log));
如果(!errlogs)errlogsnum=0;
else memcpy(&errlogs[errlogsnum-1],log,sizeof(Cl_log));
返回0;
}
/**
*@brief Cl_putlog-将消息放入带有/不带时间戳的日志文件
*@param timest-==1以放置时间戳
*@param log-指向日志结构的指针
*@param lvl-消息日志级别(如果lvl>日志级别,消息将不会打印)
*@param fmt-格式和消息的其余部分
*@返回文件中保存的符号数量
*/
int CLU putlogt(int timest、CLU log*log、CLU loglevl lvl、const char*fmt等){
如果(!log | |!log->logpath | | log->loglevel<0 | | log->loglevel>=loglevel_CNT)返回0;
如果(lvl>log->loglevel)返回0;
if(pthread\u mutex\u lock(&log->mutex))返回0;
int i=0;
文件*logfd=fopen(log->logpath,“a”);
如果(!logfd)转到rtn;
if(timest){
char-strtm[128];
时间t=时间(空);
struct tm*curtm=本地时间(&t);
strftime(strtm,128,“%Y/%m/%d-%H:%m”,curtm);
i=fprintf(logfd,“%s\t”,strtm);
}
va_列表ar;
va_启动(ar、fmt);
i+=vfprintf(logfd、fmt、ar);
va_端(ar);
fclose(logfd);
rtn:
pthread_mutex_unlock(&log->mutex);
返回i;
}
在Cl_createlog()
中,我只是测试了打开给定文件和初始化互斥的能力。在Cl\u putlogt()
中,我打开给定的文件,写入并关闭它。用于使任何线程的写入原子化的互斥体
数组
errlogs
包含用于写入关键消息的所有打开的日志。无论何时访问日志文件(包括旋转时),都可以使用互斥锁。或者您应该让每个日志文件写入事件打开并关闭日志文件(仍然使用互斥锁以防止同时访问),但是如果您做了大量日志记录,也可能会影响性能。无论哪种方式,您都应该只在关键任务之后登录每个线程,这样日志互斥不会降低您的实际功能。此外,您还希望签出现有库(例如)。