【问题标题】:Piping log output through a C program for easy log rotation通过 C 程序管道日志输出,以便于日志轮换
【发布时间】:2012-08-05 00:35:58
【问题描述】:

我正在努力让我的一些通过 bash 重定向记录的应用程序的 logrotate 变得非常容易。基本上,我有一个将 STDIN 读入缓冲区的 C 程序。它读取这个缓冲区,每当遇到换行符时,它就会将收集到的输出写入文件。

这个程序的不同之处在于它不会让文件保持打开状态。 每次遇到新行时,它都会打开它以追加。这与 logrotate 实用程序一起非常好,但我想知道是否存在某种可怕的不可预见的问题,我没有考虑到稍后会遇到的问题。

在这个实用程序中实现信号处理并让 logrotate 发送一个 SIGHUP 会更好吗?我正在做的事情是否有可怕的性能损失?

所以通常你会在哪里做:

./app >> output.log

使用记录器实用程序:

./app | ./mylogger output.log

虽然我的 C 语言太差了,但我并不十分精通它的最佳实践。任何指导将不胜感激。

来源:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define BUFSIZE 1024
#define MAX_WRITE_FAILS 3

/**
 * outputs the given content to the specified file.
 */
int file_output(char *filename, char *content, size_t content_length)
{
    FILE *fp;
    fp  =   fopen(filename, "a");
    content[content_length + 1] =   '\0';
    if(fp == NULL) return errno;
    fwrite(content, sizeof(char), content_length, fp);
    fclose(fp);
    return 0;
}

/**
 * Loops over STDIN and whenever it finds a newline, sends the current content
 * buffer to the file specified on the command line.
 */
int main(int argc, char *argv[])
{
    int i;
    char buffer[BUFSIZE];
    char *content           =   malloc(sizeof(char) * BUFSIZE);
    size_t content_size     =   0;
    int content_buf_size    =   BUFSIZE;
    int write_failures      =   0;
    char *file;

    if(argc < 2)
    {
        fprintf(stderr, "Usage: logger <file>");
        exit(1);
    }
    file    =   argv[1];

    // loop over STDIN
    while(fgets(buffer, BUFSIZE, stdin))
    {
        int output_err;
        int buflength   =   strlen(buffer);

        // loop over character for character, searching for newlines and
        // appending our buffer to the output content as we go along
        for(i = 0; i < buflength; i++)
        {
            char *old   =   content;

            // check if we have a newline or end of string
            if(buffer[i] == '\n' || buffer[i] == '\0' || (i != (buflength - 1) && buffer[i] == '\r' && buffer[i+1] == '\n'))
            {
                content[content_size]   =   '\n';
                output_err  =   file_output(file, content, content_size + 1);
                if(output_err == 0)
                {
                    // success! reset the content size (ie more or less resets
                    // the output content string)
                    content_size    =   0;
                    write_failures  =   0;
                }
                else
                {
                    // write failed, try to keep going. this will preserve our
                    // newline so that the next newline we encounter will write
                    // both lines (this AND and the next).
                    content_size++;
                    write_failures++;
                }
            }

            if(write_failures >= MAX_WRITE_FAILS)
            {
                fprintf(stderr, "Failed to write output to file %d times (errno: %d). Quitting.\n", write_failures, output_err);
                exit(3);
            }

            if(buffer[i] != '\n' && buffer[i] != '\r' && buffer[i] != '\0')
            {
                // copy buffer into content (if it's not a newline/null)
                content[content_size]   =   buffer[i];
                content_size++;
            }

            // check if we're pushing the limits of our content buffer
            if(content_size >= content_buf_size - 1)
            {
                // we need to up the size of our output buffer
                content_buf_size    +=  BUFSIZE;
                content =   (char *)realloc(content, sizeof(char) * content_buf_size);
                if(content == NULL)
                {
                    fprintf(stderr, "Failed to reallocate buffer memory.\n");
                    free(old);
                    exit(2);
                }
            }
        }
    }
    return 0;
}

谢谢!

【问题讨论】:

  • 您是否考虑过只使用 copytruncate 进行 logrotate 而不是重新发明tee
  • 没有,因为我也没听说过。谢谢,我去看看!
  • 有趣的是,copytruncate 正是我一直需要的。谢谢。
  • 我们使用logrotate 与长时间运行的守护进程一起工作,我们只需配置logrotate 以发出信号(SIGHUPSIGUSR1)和日志记录进程仅关闭和重新打开文件当收到这样的信号时。我找不到在每个输出行上关闭和打开文件的任何理由,但是如果您仍然要缓冲自己,为什么要使用 fopen() 而不是 open()
  • 请记住,在截断之前复制日志可能需要一段时间,并且两次操作之间的所有日志都将丢失 - 如手册页中所述。

标签: c bash logrotate


【解决方案1】:

由于我在 cmets 中的建议结果证明是您所需要的,因此我将其添加为答案,并提供更多解释。

当您的日志应用程序无法被告知关闭其日志文件(通常通过 SIGHUP)时,您可以在 logrotate.conf 中使用“copytruncate”选项。

这是手册页中的描述:

  Truncate  the  original log file in place after creating a copy,
  instead of moving the old log file and optionally creating a new
  one,  It  can be used when some program can not be told to close
  its logfile and thus might continue writing (appending)  to  the
  previous log file forever.  Note that there is a very small time
  slice between copying the file and truncating it, so  some  log-
  ging  data  might be lost.  When this option is used, the create
  option will have no effect, as the old log file stays in  place.

来源:http://linuxcommand.org/man_pages/logrotate8.html

【讨论】:

    猜你喜欢
    • 2013-09-10
    • 2018-12-08
    • 2023-04-04
    • 2021-11-01
    • 2020-07-15
    • 2017-03-08
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    相关资源
    最近更新 更多