The Database Managers, Inc.

Contact The Database Managers, Inc.


Use an RSS enabled news reader to read these articles.Use an RSS enabled news reader to read these articles.

Append or Create a Log File Using fopen()

by Curtis Krauskopf

Q:  How can I use fopen to append to a log file? Whenever I try to append to a file, fopen returns null (0) which says the open failed. What's wrong?

A:   The Borland C++ Builder help page for fopen() says:

Prototype:
FILE *fopen(const char *filename, const char *mode);
FILE *_wfopen(const wchar_t *filename, const wchar_t *mode);

Description:
Opens a stream.

fopen opens the file named by filename and associates a stream with it. fopen returns a pointer to be used to identify the stream in subsequent operations.

The mode string used in calls to fopen is one of the following values:

Value: Description:

r Open for reading only.
w Create for writing. If a file by that name already exists, it will be overwritten.
a Append; open for writing at end-of-file or create for writing if the file does not exist.
r+ Open an existing file for update (reading and writing).
w+ Create a new file for update (reading and writing). If a file by that name already exists, it will be overwritten.
a+ Open for append; open (or create if the file does not exist) for update at the end of the file.

To specify that a given file is being opened or created in text mode append a t to the mode string (rt w+t and so on). Similarly to specify binary mode append a b to the mode string (wb a+b and so on). fopen also allows the t or b to be inserted between the letter and the + character in the mode string; for example rt+ is equivalent to r+t.

If a t or b is not given in the mode string the mode is governed by the global variable _fmode. If _fmode is set to O_B/INARY files are opened in binary mode. If _fmode is set to O_TEXT they are opened in text mode. These O_... constants are defined in fcntl.h.

When a file is opened for update, both input and output can be done on the resulting stream; however, output cannot be directly followed by input without an intervening fseek or rewind input cannot be directly followed by output without an intervening fseek, rewind, or an input that encounters end-of-file

Return Value:
On successful completion fopen returns a pointer to the newly opened stream. In the event of error it returns NULL.

The descriptions for both the "a" and "a+" append modes say that the file will be created if it doesn't exist. I've found, in practice, that this doesn't work. If the file doesn't exist when fopen is called with a "a" or "a+" open mode, the file will not be created.

So, you might think, "well, I'll cover all my choices by using "wa" or "wa+" -- that way it will create when the file doesn't exist and it will append when the file does exist."

That's a great idea but unfortunately, it doesn't work either.

The best way to open an existing file for appending and to create a file that doesn't exist is to make two separate calls to fopen(). The first call tries to append to the file. If that fails (when fopen() returns 0), then try opening the file using the "w" mode. See listing 1.

#include <stdio.h>

...

void logit() {
    FILE *log = fopen("logfile.txt", "at");
    if (!log) log = fopen("logfile.txt", "wt");
    if (!log) {
        printf("can not open logfile.txt for writing.\n");
        return;   // bail out if we can't log
    }

    // Insert whatever you want logged here

    fclose(log);
}
Listing 1: Create a file or open it for appending.

This works but it has one little problem that you'll only notice at runtime. In my test program, logit() was called around 2000 times and the program took about 12 seconds longer to run. That's okay if you only need to run the program like that a few times but I needed to run this program hundreds of times in a batch file.

To solve this problem, I changed log to be a static variable. The code checks if log has been opened -- if not, the file is opened and log is initialized to the file's handle. Since the file is open throughout the program's execution, I don't need to close it anymore so I've commented-out the fclose(log) call at the bottom of logit().

Listing 2 shows the final logit() function. This one has almost no execution time overhead.

#include <stdio.h>

...

void logit() {
    static FILE *log = 0;

    if (log == 0) {
        log = fopen("logfile.txt", "at");
        if (!log) log = fopen("logfile.txt", "wt");
        if (!log) {
            printf("can not open logfile.txt for writing.\n");
            return;   // bail out if we can't log
        }
    }

    // Insert whatever you want logged here

    // The next line is commented-out on purpose...
    // fclose(log);
}
Listing 2: A static solution that runs faster.
As with most solutions, there is a downside. Because fopen() usually buffers the output, If the program were to crash, the log file would be incomplete and could mislead you about what the program was doing when it crashed. If having an up-to-date log file is important, add an fflush() call to the parts of the logit() function that involve important log messages. Of course, that will incur an execution-time cost, but at least the log file will be accurate.

Conclusion:

Despite the documentation, fopen() does not create files that don't exist when using any of the append modes. The best solution is to make two fopen() calls: the first one tries to append to an existing file and the second one creates the file if it doesn't exist.

Because of the overhead involved in opening and closing a file, it's much better to open the file one time and keep it open for the rest of the program than it is to only have the file open when it's being written to.

If an accurate and up-to-date log file is mandatory, use fflush() to guarantee that the output buffer is flushed before leaving any logging functions.

This article was written by Curtis Krauskopf (email at ).

Copyright 2003-2010 The Database Managers, Inc.


Popular C++ topics at The Database Managers:

C++ FAQ Services | Programming | Contact Us | Recent Updates
Send feedback to: