Zhi Wang
Zhi Wang

Reputation: 1168

Concurrent log file access in C/C++

I am creating a multiple threads program and several threads may need to call a global function

writeLog(const char* pMsg);

and the writeLog will be implemented something like tihs:

void writeLog(const char* pMsg)
{
   CRITICAL_SECTION cs;

   // initialize critical section
   ...

   EnterCriticalSection(&cs);

   // g_pLogFilePath is a global variable.
   FILE *file;
   if (0!=fopen_s(&file, g_pLogFilePath, "r+"))  
      return;

   fprintf(file, pMsg);

   fclose(file):
   LeaveCriticalSection(&cs);
}

My questions are:

1) is it the best way to do concurrent logging? i.e., using critical section.

2) since I will write log in many places in the threads, 
and since each log writing will involve open/close file,
does the io will impact the performance significantly?

Thanks!

Upvotes: 0

Views: 2650

Answers (4)

Martin James
Martin James

Reputation: 24887

A CS is a reasonable way to protect the logging, yes. To avoid inflicting the open/write/close upon every call from every thread, it's common to queue off the string, (if not already malloced/newed, you may need to copy it), to a separate log thread. Blocking disk delays are then buffered from the logging calls. Any lazy-writing etc. optimizations can be implemented in the log thread.

Alternatively, as suggested by the other posters, just use a logging framework that has all this stuff already implemented.

Upvotes: 2

paddy
paddy

Reputation: 63481

I was writing an answer, then a circuit breaker tripped. Since my answer is still in draft I may as well continue. Much the same as the answer that provides a singleton class, but I do it a little more C-like. This is all in a separate source file (Logging.cpp for example).

static CRITICAL_SECTION csLogMutex;
static FILE *fpFile = NULL;
static bool bInit = false;

bool InitLog( const char *filename )
{
    if( bInit ) return false;
    bInit = true;
    fpFile = fopen( filename, "at" );
    InitializeCriticalSection(&csLogMutex);
    return fpFile != NULL;
}

void ShutdownLog()
{
    if( !bInit ) return;
    if( fpFile ) fclose(fpFile);
    DeleteCriticalSection(&csLogMutex);
    fpFile = NULL;
    bInit = false;
}

Those are called in your application entry/exit... As for logging, I prefer to use variable argument lists so I can do printf-style logging.

void writeLog(const char* pMsg, ...)
{
   if( fpFile == NULL ) return;

   EnterCriticalSection(&csLogMutex);

   // You can write a timestamp into the file here if you like.

   va_list ap;
   va_start(ap, pMsg);
   vfprintf( fpFile, pMsg, ap );
   fprintf( fpFile, "\n" );        // I hate supplying newlines to log functions!
   va_end(ap);

   LeaveCriticalSection(&csLogMutex);
}

If you plan to do logging within DLLs, you can't use this static approach. Instead you'll need to open the file with _fsopen and deny read/write sharing.

You may wish to periodically call fflush too, if you expect your application to crash. Or you'll have to call it every time if you want to externally monitor the log in realtime.

Yes, there's a performance impact from critical sections but it's nothing compared to the performance cost of writing to files. You can enter critical sections many thousands of times per second without a worry.

Upvotes: 1

Brady
Brady

Reputation: 10357

To answer your questions:

  1. Yes, a critical section is indeed needed for concurrent logging.

  2. Yes, logging may indeed affect performance.

As mentioned in the comments, the object used to "protect" the critical section must be accessible by all threads, such as a global variable or singleton.

Regarding the logging performance, IO can be costly. One common approach is to have a logging object that buffers the messages to be logged, and only writes when the buffer is full. This will help with the performance. Additionally, consider having several log levels: DEBUG, INFO, WARNING, ERROR.

Upvotes: 3

nogard
nogard

Reputation: 9716

The best way to do concurrent logging is to use one of the existing log library for C++. They have many features you would probably like to use (different appenders, formatting, concurrency etc).

If you still want to have your own solution you could probably have something like this: simple singleton that initialized once and keeps the state (file handler and mutex)

class Log
{
public:

    // Singleton
    static Log & getLog() 
    {
        static Log theLog;
        return theLog;
    }

    void log(const std::string & message)
    {
         // synchronous writing here
    }
private:
    // Hidden ctor
    Log()
    {
         // open file ONCE here
    }

    // Synchronisation primitive - instance variable
    // CRITICAL_SECTION or Boost mutex (preferable)
    CRITICAL_SECTION cs_;

    // File handler: FILE * or std::ofstream
    FILE * handler_;
};

Upvotes: 4

Related Questions