C 与logrotate兼容的日志记录
我正在编写一个Linux守护进程来编写日志。我想把原木按原木旋转。程序是用C语言编写的 通常,我的程序会在启动时打开日志文件,然后根据需要写入条目,最后在退出时关闭日志文件 为了使用logrotate支持日志旋转,我需要做哪些不同的事情?据我所知,每次logrotate完成工作时,我的程序都应该能够重新打开日志文件。然而,我在谷歌上搜索的源代码并没有明确说明重新打开日志文件的确切含义。我需要对旧文件做些什么吗?我可以创建另一个同名文件吗?我更喜欢非常具体的说明,比如一些简单的示例代码 我也明白应该有一种方法告诉我的程序什么时候可以重新打开。我的程序已经有了一个D-Bus接口,我想用它来做这些通知 注意:我不需要关于如何配置logrotate的说明。这个问题只是关于如何使我自己的软件与之兼容 通常,我的程序会在启动时打开日志文件,然后 根据需要写入条目,然后在退出时关闭日志文件 为了支持日志轮换,我需要做哪些不同的事情 使用logrotate 不,您的程序应该像不了解logrotate一样工作 我需要对旧文件做些什么吗?我可以创建另一个同名文件吗C 与logrotate兼容的日志记录,c,linux,logrotate,C,Linux,Logrotate,我正在编写一个Linux守护进程来编写日志。我想把原木按原木旋转。程序是用C语言编写的 通常,我的程序会在启动时打开日志文件,然后根据需要写入条目,最后在退出时关闭日志文件 为了使用logrotate支持日志旋转,我需要做哪些不同的事情?据我所知,每次logrotate完成工作时,我的程序都应该能够重新打开日志文件。然而,我在谷歌上搜索的源代码并没有明确说明重新打开日志文件的确切含义。我需要对旧文件做些什么吗?我可以创建另一个同名文件吗?我更喜欢非常具体的说明,比如一些简单的示例代码 我也明白应
不可以。应该只打开和写入一个日志文件。Logrotate将检查该文件,如果文件太大,它会复制/保存旧部件,并截断当前日志文件。因此,您的程序应该是完全透明的—它不需要了解任何有关logrotate的信息。有几种常见的方法:
logrotate
时,程序应该能够捕获一个信号(通常是SIGHUP)作为关闭和重新打开其日志文件的请求。然后logrotate
在postrotate脚本中发送信号logrotate
,而您的程序并不知道它,但可以重新启动。然后logrotate
在postrotate脚本中重新启动程序。缺点:如果程序的启动成本很高,这可能是次优的logrotate
,而您的程序并不知道它,但是您将copyruncate选项传递给logrotate
。然后logrotate
复制文件,然后将其截断。缺点:在比赛条件下,你可能会丢失信息。从rotatelog.conf
manpage
。。。请注意,复制文件和截断文件之间的时间间隔很短,因此一些日志数据可能会丢失
rotatelogs
。与直接写入文件不同,您可以通过编程将其日志传输到rotatelogs
。然后rotatelogs
管理不同的日志文件。缺点:您的程序应该能够登录到管道,否则您将需要安装一个命名的fifo但是要注意,对于关键日志,在每条消息之后关闭文件可能会很有趣,因为这样可以确保在应用程序崩溃的情况下,所有内容都已到达磁盘。虽然
man logrotate
示例使用HUP信号,但我建议使用USR1
或USR2
,因为使用HUP是常见的例如,在logrotate配置文件中
/var/log/yourapp/log {
rotate 7
weekly
postrotate
/usr/bin/killall -USR1 yourapp
endscript
}
一个棘手的问题是处理信号到达日志中间的情况。事实上,没有锁定原语(除了<代码> SEMIN POST)<代码>,这是一个有趣的问题。
最简单的方法是使用一个专用线程等待,所有线程中的信号都被阻塞。在退出时,进程本身发送信号,并加入专用线程。例如#define ROTATE_SIGNAL SIGUSR1
static pthread_t log_thread;
static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
static char *log_path = NULL;
static FILE *volatile log_file = NULL;
int log(const char *format, ...)
{
va_list args;
int retval;
if (!format)
return -1;
if (!*format)
return 0;
va_start(args, format);
pthread_mutex_lock(&log_lock);
if (!log_file)
return -1;
retval = vfprintf(log_file, format, args);
pthread_mutex_unlock(&log_lock);
va_end(args);
return retval;
}
void *log_sighandler(void *unused)
{
siginfo_t info;
sigset_t sigs;
int signum;
sigemptyset(&sigs);
sigaddset(&sigs, ROTATE_SIGNAL);
while (1) {
signum = sigwaitinfo(&sigs, &info);
if (signum != ROTATE_SIGNAL)
continue;
/* Sent by this process itself, for exiting? */
if (info.si_pid == getpid())
break;
pthread_mutex_lock(&log_lock);
if (log_file) {
fflush(log_file);
fclose(log_file);
log_file = NULL;
}
if (log_path) {
log_file = fopen(log_path, "a");
}
pthread_mutex_unlock(&log_lock);
}
/* Close time. */
pthread_mutex_lock(&log_lock);
if (log_file) {
fflush(log_file);
fclose(log_file);
log_file = NULL;
}
pthread_mutex_unlock(&log_lock);
return NULL;
}
/* Initialize logging to the specified path.
Returns 0 if successful, errno otherwise. */
int log_init(const char *path)
{
sigset_t sigs;
pthread_attr_t attrs;
int retval;
/* Block the rotate signal in all threads. */
sigemptyset(&sigs);
sigaddset(&sigs, ROTATE_SIGNAL);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
/* Open the log file. Since this is in the main thread,
before the rotate signal thread, no need to use log_lock. */
if (log_file) {
/* You're using this wrong. */
fflush(log_file);
fclose(log_file);
}
log_file = fopen(path, "a");
if (!log_file)
return errno;
log_path = strdup(path);
/* Create a thread to handle the rotate signal, with a tiny stack. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(65536);
retval = pthread_create(&log_thread, &attrs, log_sighandler, NULL);
pthread_attr_destroy(&attrs);
if (retval)
return errno = retval;
return 0;
}
void log_done(void)
{
pthread_kill(log_thread, ROTATE_SIGNAL);
pthread_join(log_thread, NULL);
free(log_path);
log_path = NULL;
}
其思想是在main()
中,在记录或创建任何其他线程之前,调用log\u init(日志文件路径)
,注意日志文件路径的副本已保存。它设置信号掩码(由您可能创建的任何线程继承),并创建辅助线程。退出之前,调用log\u done()
。要将某些内容记录到日志文件中,请像使用printf()
一样使用log()
我个人也会在vfprintf()
行之前自动添加一个时间戳:
struct timespec ts;
struct tm tm;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0 &&
localtime_r(&(ts.tv_sec), &tm) == &tm)
fprintf(log_file, "%04d-%02d-%02d %02d:%02d:%02d.%03ld: ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
ts.tv_nsec / 1000000L);
这种
YYYY-MM-DD HH:MM:SS.sss
格式有一个很好的优点,即它接近全球标准(),并按正确的顺序排序。请注意,这需要“copyruncate”“logrotate配置-是在无法告知日志程序重新打开其日志文件时使用的回退解决方案。程序必须以附加模式打开日志文件才能工作,并且可能会丢失日志消息。更好的方法是让logrotate与程序交互(例如,向其发送unix信号,或使用其他程序细节),以便在程序旋转时关闭并重新打开日志文件。