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:
|