3

I have come across many questions related to this but my requirement is quite different. I developed a C application(mib-test.c) earlier which uses fprintf at lot of places. fprintf logs the messages in a file.

But now I need to print these messages to console also. I am looking for a solution in which I do not need to change fprintf in all the places.

Is there a way to do it?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Raxesh Oriya
  • 383
  • 6
  • 27

3 Answers3

2

No, there is not, you will have to edit each instance. A better approach to begin with would have been to create your own logging function or macro. I would suggest you to do that now, and save yourself some time if you ever change mind again in the future.

Example using a function:

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);
}

Example using a macro:

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

See also: do { ... } while (0) — what is it good for?

Note that using a macro will evaluate expressions twice. For example log("%d\n", get_int()); using the macro form will call get_int() twice. You should only use the macro if you plan to pass simple values.

Both solutions assume that logfile is global and already opened for writing. You could actually define it static inside the function and handle the initial opening checking if (logfile == NULL) before doing anything.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • This evaluates the macro arguments twice. – Eric Postpischil Oct 12 '20 at 13:38
  • Hmmm, I don't get your macro to work: https://onlinegdb.com/Hyv9JkGPP – klutt Oct 12 '20 at 13:52
  • @klutt you need at least one argument after the format string (otherwise why use `fprintf` in the first place?). You'd need another macro if you want to also handle the case of zero additional args. – Marco Bonelli Oct 12 '20 at 13:55
  • Then I definitely prefer the function. Requiring an extra argument seems a bit meh. Furthermore, you should name it something else than `log` since that function exists in math.h – klutt Oct 12 '20 at 14:15
  • @klutt of course if you don't need to output any arguments then just hard code the format string: `#define log(...) do { fprintf(stderr, "%s\n", __VA_ARGS__); ...` – phuclv Oct 12 '20 at 15:01
1

@marcobonelli wrote a great answer. So for the replacing part, if you're not using an IDE that can do all the replacements for you, you could probably use this simple sed command to do all the replacement. Assume you have a log function like in this answer, then just do this:

sed 's/fprintf(logfile/log(/g' -i <file>
klutt
  • 30,332
  • 17
  • 55
  • 95
1

This is incomplete (error handling is omitted), but it demonstrates the principle of replacing filedescriptors via dup() and 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;
}

Note: diagnostic output should go to stderr. If you keep it that way, it can always be replaced by a method like above.

[It is also possible to fetch the fileno() from the fopen'd log file, and do it the other way around. YMMV]

wildplasser
  • 43,142
  • 8
  • 66
  • 109