我正在编写一个写日志的Linux守护程序。我希望通过logrotate旋转日志。该程序是用C编写的。
通常,我的程序会在启动时打开日志文件,然后根据需要写入条目,然后最后在退出时关闭日志文件。
为了使用logrotate支持日志轮换,我需要做些什么不同的事情?据我了解,每次logrotate完成工作时,我的程序都应该能够重新打开日志文件。但是,我搜索过的资源没有指定重新打开日志文件的确切含义。我需要对旧文件做些什么,是否可以创建另一个具有相同名称的文件?我希望使用非常具体的说明,例如一些简单的示例代码。
我还理解应该有一种方法可以告诉我的程序何时该重新打开。我的程序已经有一个D-Bus界面,我考虑将其用于那些通知。
注意:我不需要有关如何配置logrotate的说明。这个问题只是关于如何使自己的软件与之兼容。
答案 0 :(得分:3)
有几种常用方法:
logrotate
,并且您的程序应该能够捕获一个信号(通常为SIGHUP)作为关闭并重新打开其日志文件的请求。然后logrotate
用后旋转脚本发送信号logrotate
并且您的程序不知道它,但是可以重新启动。然后logrotate
用postrotate脚本重新启动程序。缺点:如果程序启动费用昂贵,则可能不是最佳选择您使用logrotate
,但您的程序不知道它,但是您将copytruncate选项传递给logrotate
。然后logrotate
复制文件,然后将其截断。缺点:在比赛条件下,您可能会丢失消息。来自rotatelog.conf
联机帮助页
...请注意,复制文件和截断文件之间的时间间隔非常短,因此某些日志记录数据可能会丢失...
您使用rotatelogs
,它是httpd Apache的实用程序。无需直接写入文件,您可以将其日志通过管道传输到rotatelogs
。然后rotatelogs
管理不同的日志文件。缺点:您的程序应该能够登录到管道,或者您需要安装一个名为fifo的计算机。
但是要注意,对于关键日志,在每条消息后关闭文件可能会很有趣,因为这样可以确保在应用程序崩溃时所有内容都到达磁盘。
答案 1 :(得分:2)
通常,我的程序会在启动时打开日志文件,然后 根据需要编写条目,然后最后在退出时关闭日志文件。
为了支持日志轮转,我需要做些什么不同的事情 使用logrotate吗?
否,您的程序应该就好像对logrotate一无所知。
我需要对旧文件做些什么,我可以只创建一个同名文件吗?
不。应该只有一个日志文件可以打开和写入。 Logrotate将检查该文件,如果文件太大,它将复制/保存旧部分,并截断当前日志文件。因此,您的程序应完全透明地工作-不需要了解logrotate。
答案 2 :(得分:2)
尽管man logrotate
示例使用了HUP信号,但我还是建议使用USR1
或USR2
,因为通常将HUP用于“重载配置”。因此,在logrotate配置文件中,例如,
/var/log/yourapp/log {
rotate 7
weekly
postrotate
/usr/bin/killall -USR1 yourapp
endscript
}
棘手的位是处理信号在记录中间到达的情况。除了sem_post()
以外,其他所有锁定原语都没有async-signal safe的事实,这使它成为一个有趣的问题。
最简单的方法是使用专用线程,在sigwaitinfo()
中等待,并在所有线程中阻塞信号。在退出时,该进程自身发送信号,并加入专用线程。例如,
#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_init(path-to-log-file)
,注意已保存了日志文件路径的副本。它设置信号掩码(由您可能创建的任何线程继承),并创建帮助程序线程。退出之前,请致电log_done()
。要将某些内容记录到日志文件中,请像使用log()
一样使用printf()
。
我个人也会在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
格式的好处是它接近全球标准(ISO 8601)并以正确的顺序排序。