C语言如何在控制台和文件中同时打印日志?

How to print logs on both console and file in C language?

我遇到过很多与此相关的问题,但我的要求却大不相同。我之前开发了一个 C 应用程序(mib-test.c),它在很多地方使用了 fprintffprintf 将消息记录在文件中。

但现在我还需要将这些消息打印到控制台。我正在寻找一种不需要在所有地方更改 fprintf 的解决方案。

有办法吗?

不,没有,您将不得不编辑每个实例。更好的开始方法是创建您自己的日志记录函数或宏。我建议您现在就这样做,如果您将来再次改变主意,可以节省一些时间。

使用函数的示例:

void log(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);

    va_start(args, fmt);
    vfprintf(logfile, fmt, args);
    va_end(args);
}

使用宏的示例:

#define log(fmt, ...) do {                                    \
                          fprintf(stderr, fmt, __VA_ARGS__);  \
                          fprintf(logfile, fmt, __VA_ARGS__); \
                      } while (0)

另请参阅:do { ... } while (0) — what is it good for?

请注意 使用宏会计算表达式两次。例如 log("%d\n", get_int()); 使用宏形式将调用 get_int() 两次。如果您打算传递简单的值,您应该只使用宏。

两种解决方案都假定 logfile 是全局的并且已经打开以供写入。您实际上可以在函数内定义它 static 并在执行任何操作之前处理初始打开检查 if (logfile == NULL)

@marcobonelli 写了一个很好的答案。因此对于替换部分,如果您没有使用可以为您完成所有替换的 IDE,您可能可以使用这个简单的 sed 命令来完成所有替换。假设你有一个像 中的 log 函数,那么就这样做:

sed 's/fprintf(logfile/log(/g' -i <file>

这个不完整(省略了错误处理),但它演示了通过dup()dup2()替换文件描述符的原理:


#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>

int errtee(char * path);

        /*
        ** Function to write to *both* stderr and a named file
        ** Return the new filedescriptor, which should be
        ** used instead of STDERR_FILENO.
        ** (or -1 on error)
        */
int errtee(char * path){

    int out[2];
    pipe(out);
    int pid=fork();
    if(pid<0){ // error
        perror( "fork() failed\n" );
        return -1;
        }
    if(pid>0){ // parent
        close(out[0]);
        return out[1];
    }
    if(pid==0){ // child
        int fd_extra;
        char buff[512];

        fd_extra = open(path, O_CREAT | O_WRONLY, 0666);
        if (fd_extra < 0) return -1;
        close(out[1]);
        dup2(out[0], STDIN_FILENO);
        close(out[0]);
        while(1) {
                unsigned done, todo;
                int cnt, this;
                cnt = read(STDIN_FILENO, buff, sizeof buff) ;
                if (cnt <=0) break;
                for(todo = cnt, done = 0; done < todo; done += this) {
                        this = write(STDERR_FILENO, buff+done, todo-done);
                        if (cnt <=0) break;     // this is not perfect ...
                        }

                for(todo = cnt, done = 0; done < todo; done += this) {
                        this = write(fd_extra, buff+done, todo-done);
                        if (cnt <=0) break;     // this is not perfect ...
                        }
                }
        close( fd_extra);
    }
 exit(0);
}

int main(void){
    int new_fd;
    int old_fd;
    char buff[222];

    new_fd = errtee("Mylogfile.log" );
        if (new_fd < 0) {
        perror( "errtee() failed\n" );
        exit(1);
        }
        // Save the stdin filedescriptor
    old_fd = dup( STDERR_FILENO);
        // ... and replace it by the one from the subprocess.
    dup2(new_fd, STDERR_FILENO);

        // Send some output to stderr ...
    while (fgets(buff, sizeof buff, stdin) ) {
        fprintf(stderr, "%s", buff);
        }

        // Restore the old stdin filedescriptor
    dup2(old_fd, STDERR_FILENO);

        // ... and check if stderr still works  ...
    fprintf(stderr,"Completed\n" );
    return 0;
}

注意:诊断输出应该转到 stderr。如果你一直这样,它总是可以用上面的方法代替。

[也可以从 fopen 的日志文件中获取 fileno(),并以相反的方式进行。 YMMV]