C语言如何在控制台和文件中同时打印日志?
How to print logs on both console and file in C language?
我遇到过很多与此相关的问题,但我的要求却大不相同。我之前开发了一个 C 应用程序(mib-test.c
),它在很多地方使用了 fprintf
。 fprintf
将消息记录在文件中。
但现在我还需要将这些消息打印到控制台。我正在寻找一种不需要在所有地方更改 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]
我遇到过很多与此相关的问题,但我的要求却大不相同。我之前开发了一个 C 应用程序(mib-test.c
),它在很多地方使用了 fprintf
。 fprintf
将消息记录在文件中。
但现在我还需要将这些消息打印到控制台。我正在寻找一种不需要在所有地方更改 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]