Currently I need to implement a thread-safe application logger under MS Visual C++. The main purpose is to redirect debug_printf and exception strings (thown by C++ classes) to files so I can find out what's wrong with my Win32 service program, and the max log file size can be controlled so that disk space will not be exhausted by too many debug output. __FILE__ , __LINE__ and date/time are
mandatory parameters for the logger class or function.
Unfortunately MS C++ compiler is not C99-compatible so it doesn't support variable-argument macros using __VA_ARGS__ as GCC does. After some search, I found some useful info, but some of those implementations are not thread-safe, for example, the following two printf() can be interrupted in multi-threaded environment, and the log file size can't be controlled.
http://jxta-c.jxta.org/source/browse/jxta-c/include/Attic/jxta_debug.h?logsort=date&search=&hideattic=1&sortby=file&hidecvsroot=1&diff_format=h&r1=1.2
Code:
#define JXTA_LOG(x) \
printf("%s:%d ", __FILE__, __LINE__), printf x
"Calls" to JXTA_LOG would look like:
JXTA_LOG((a1, a2, a3, ...));
http://forum.sources.ru/index.php?showtopic=63242&view=showall
The above Russian post is quite good, especially the CMyPrintf and MYprintfFN implementations. But it seems that they can't be used in c++ "throw" environment. For example, I want to log the following exception string to file with __FILE__ and __LINE__.
Code:
if ((f = fopen(FileName, "rb")) == NULL)
{
throw MyException("can't open file: %s\n", FileName);
}
Anyone has any experience with suck tricks?
I have implement similar logger, but it is not so elegant and can't be used in throw environment either.
Code:
class Logger
{
public:
Logger();
~Logger();
static void LogPrefix(const TCHAR *File, int Line);
static void Log(const TCHAR *Format, ...);
private:
#ifdef LOG_TO_FILE
static void OpenFile(const char *mode);
static const long MAX_LOG_FILE_SIZE;
#endif
static FILE *m_File;
static DWORD m_LogCount;
static CCriticalSection m_FileLock;
};
#define LOG(x) ( \
Logger::LogPrefix(__FILE__, __LINE__), \
(void)(x) \
)
#ifdef LOG_TO_FILE
const long Logger::MAX_LOG_FILE_SIZE = 2 * 1024 * 1024;
FILE * Logger::m_File = NULL;
#else
FILE * Logger::m_File = stderr;
#endif
DWORD Logger::m_LogCount = 0;
CCriticalSection Logger::m_FileLock;
Logger::Logger()
{
#ifdef LOG_TO_FILE
OpenFile("at");
#else
m_File = stderr;
#endif
}
Logger::~Logger()
{
#ifdef LOG_TO_FILE
if (m_File)
{
fclose(m_File);
m_File = NULL;
}
#endif
}
#ifdef LOG_TO_FILE
void Logger::OpenFile(const char *mode)
{
TCHAR LogFileName[MAX_PATH];
GetModuleFileNameA(NULL, LogFileName, sizeof(LogFileName)/sizeof(LogFileName[0]));
strncpy(LogFileName, g_pVar->m_AppPath.c_str(), sizeof(LogFileName) - 1);
LogFileName[sizeof(LogFileName) - 1] = '\0';
strncat(LogFileName, "\\logcenter.log", sizeof(LogFileName) - g_pVar->m_AppPath.size() - 2);
m_File = fopen(LogFileName, mode);
}
#endif
void Logger::LogPrefix(const TCHAR *File, int Line)
{
m_FileLock.Lock();
#ifdef LOG_TO_FILE
if (NULL == m_File) OpenFile("at");
if (m_LogCount > 100)
{
if (ftell(m_File) > MAX_LOG_FILE_SIZE)
{
fclose(m_File);
OpenFile("wt");
}
m_LogCount = 0;
}
#endif
for(signed int k = strlen(File); k >= 0; k--)
{
if (File[k] == '\\')
{
break;
}
}
k++;
TCHAR Prefix[512];
SYSTEMTIME LocalTime;
GetLocalTime(&LocalTime);
wsprintf(Prefix, _T("%04d-%02d-%02d %02d:%02d:%02d %s %d"),
LocalTime.wYear, LocalTime.wMonth, LocalTime.wDay,
LocalTime.wHour, LocalTime.wMinute, LocalTime.wSecond,
File + k, Line);
fprintf(m_File, _T("%s "), Prefix);
}
void Logger::Log(const TCHAR *Format, ...)
{
TCHAR ErrorMsg[1024];
va_list ap;
va_start(ap, Format);
_vsntprintf(ErrorMsg, sizeof(ErrorMsg)/sizeof(ErrorMsg[0]), Format, ap);
fprintf(m_File, _T("%s"), ErrorMsg);
m_LogCount++;
m_FileLock.Unlock();
}