-// LogFile.cpp: implementation of the CLogFile and CSQL LogFile classes.
-//
-//////////////////////////////////////////////////////////////////////
+/**
+ * @file LogFile.h
+ *
+ * @brief Implementation file for CLogFile
+ *
+ */
+// RCS ID line follows -- this is updated by CVS
+// $Id$
#include "stdafx.h"
#include "LogFile.h"
#define new DEBUG_NEW
#endif
-//////////////////////////////////////////////////////////////////////
-// Construction/Destruction
-//////////////////////////////////////////////////////////////////////
+static const TCHAR MutexName[] = _T("WINMERGE_LOG_MUTEX");
-CLogFile::CLogFile(LPCTSTR szLogName, LPCTSTR szLogPath /*= NULL*/, BOOL bDeleteExisting /*=FALSE*/)
- : m_nMaxSize(1024576)
+/**
+ * @brief Constructor
+ */
+CLogFile::CLogFile(LPCTSTR szLogName /*= NULL*/,
+ LPCTSTR szLogPath /*= NULL*/, BOOL bDeleteExisting /*=FALSE*/)
+ : m_nMaxSize(1024 * 1024) // 1MB
, m_bEnabled(FALSE)
-// CString m_strLogPath
+ , m_nDefaultLevel(LOGLEVEL::LMSG)
+ , m_nMaskLevel(LOGLEVEL::LALL)
{
+ SetFile(szLogName, szLogPath, bDeleteExisting);
+ m_hLogMutex = CreateMutex(NULL, FALSE, MutexName);
+}
- TCHAR temp[MAX_PATH];
-
+/**
+ * @brief Destructor
+ */
+CLogFile::~CLogFile()
+{
+ EnableLogging(FALSE);
+ CloseHandle(m_hLogMutex);
+}
+/**
+ * @brief Set logfilename
+ * @param bDelExisting If TRUE, existing logfile with same name
+ * is deleted.
+ */
+CString CLogFile::SetFile(CString strFile, CString strPath,
+ BOOL bDelExisting /*= FALSE*/)
+{
+ TCHAR temp[MAX_PATH] = {0};
// build the path to the logfile
- if (szLogPath != NULL
- && *szLogPath != _T('\0'))
- m_strLogPath = szLogPath;
+ if (!strPath.IsEmpty())
+ m_strLogPath = strFile;
else
{
- GetTempPath(MAX_PATH, temp);
- m_strLogPath = temp;
+ if (GetTempPath(MAX_PATH, temp))
+ m_strLogPath = temp;
}
+
if (m_strLogPath.Right(1) != _T('\\'))
m_strLogPath += _T('\\');
- m_strLogPath += szLogName;
+ m_strLogPath += strFile;
- if (bDeleteExisting)
+ if (bDelExisting)
DeleteFile(m_strLogPath);
+
+ return m_strLogPath;
+}
- // write start banner
+/**
+ * @brief Enable/Disable writing log
+ */
+void CLogFile::EnableLogging(BOOL bEnable)
+{
CTime t = CTime::GetCurrentTime();
- CString s = t.Format(_T("Begin Log: %A, %B %d, %Y %H:%M:%S"));
- Write(_T("\n\n==========================================================================\n")
- _T("==========================================================================\n")
- _T("%s\n==========================================================================\n")
- _T("==========================================================================\n"), s);
- Write(m_strLogPath);
+ CString s = t.Format(_T(" %A, %B %d, %Y %H:%M:%S"));
+
+ if (bEnable)
+ {
+ m_bEnabled = TRUE;
+ Write(_T("\n*******\nLog Started: %s"), s);
+ Write(_T("Path: %s\n*******\n"), m_strLogPath);
+ }
+ else
+ {
+ m_bEnabled = FALSE;
+ Write(_T("\n*******\nLog Stopped: %s\n"), s);
+ Write(_T("*******\n"));
+ }
}
-CLogFile::~CLogFile()
+/**
+ * @brief Return default level for log messages
+ */
+UINT CLogFile::GetDefaultLevel() const
+{
+ return m_nDefaultLevel;
+}
+
+/**
+ * @brief Set default level for log messages
+ */
+void CLogFile::SetDefaultLevel(UINT logLevel)
{
- Write(_T("##### End Log #####\n"));
+ m_nDefaultLevel = logLevel;
}
+/**
+ * @brief Get log message mask.
+ *
+ * Mask allows to select which level messages are written to log.
+ */
+UINT CLogFile::GetMaskLevel() const
+{
+ return m_nMaskLevel;
+}
+
+/**
+ * @brief Set log message mask
+ */
+void CLogFile::SetMaskLevel(UINT maskLevel)
+{
+ m_nMaskLevel = maskLevel;
+}
+
+/**
+ * @brief Write formatted message with default log level
+ */
void CLogFile::Write(LPCTSTR pszFormat, ...)
{
if (!m_bEnabled)
return;
- TCHAR buf[2048]=_T("");
+ if ((m_nDefaultLevel & m_nMaskLevel) == 0)
+ return;
+
+ TCHAR buf[2*1024] = {0};
va_list arglist;
va_start(arglist, pszFormat);
if (pszFormat != NULL)
_vstprintf(buf, pszFormat, arglist);
va_end(arglist);
_tcscat(buf, _T("\n"));
- //TRACE(buf);
+
+ CString msg = GetPrefix(m_nDefaultLevel);
+ msg += buf;
- FILE *f;
- if ((f=_tfopen(m_strLogPath, _T("a"))) != NULL)
- {
- _fputts(buf, f);
+ Write(msg);
+}
+
+/**
+ * @brief Write message from resource with default level
+ */
+void CLogFile::Write(DWORD idFormatString, ...)
+{
+ if (!m_bEnabled)
+ return;
+
+ if ((m_nDefaultLevel & m_nMaskLevel) == 0)
+ return;
+
+ TCHAR buf[2*1024]=_T("");
+ CString strFormat;
- // prune the log if it gets too big
- if (ftell(f) >= (int)m_nMaxSize)
- Prune(f);
- else
- fclose(f);
+ if (strFormat.LoadString(idFormatString))
+ {
+ va_list arglist;
+ va_start(arglist, idFormatString);
+ _vstprintf(buf, strFormat, arglist);
+ va_end(arglist);
+ _tcscat(buf, _T("\n"));
+
+ CString msg = GetPrefix(m_nDefaultLevel);
+ msg += buf;
+
+ Write(msg);
}
}
-void CLogFile::Write(DWORD idFormatString, ...)
+/**
+ * @brief Write formatted message to log with given level
+ */
+void CLogFile::Write(UINT level, LPCTSTR pszFormat, ...)
{
if (!m_bEnabled)
return;
- TCHAR buf[2048]=_T("");
+ if ((level & m_nMaskLevel) == 0)
+ return;
+
+ TCHAR buf[4*1024] = {0};
+ va_list arglist;
+ va_start(arglist, pszFormat);
+ if (pszFormat != NULL)
+ _vstprintf(buf, pszFormat, arglist);
+ va_end(arglist);
+ _tcscat(buf, _T("\n"));
+
+ CString msg = GetPrefix(level);
+ msg += buf;
+
+ Write(msg);
+}
+
+/**
+ * @brief Write message from resource to log with given level
+ */
+void CLogFile::Write(UINT level, DWORD idFormatString, ...)
+{
+ if (!m_bEnabled)
+ return;
+
+ if ((level & m_nMaskLevel) == 0)
+ return;
+
+ TCHAR buf[2*1024]=_T("");
CString strFormat;
if (strFormat.LoadString(idFormatString))
_vstprintf(buf, strFormat, arglist);
va_end(arglist);
_tcscat(buf, _T("\n"));
+
+ CString msg = GetPrefix(m_nDefaultLevel);
+ msg += buf;
+
+ Write(msg);
+ }
+}
+/**
+ * @brief Write new line to log.
+ * @note this function is used only internally by other write-functions.
+ */
+void CLogFile::Write(CString msg)
+{
+ DWORD dwWaitRes = WaitForSingleObject(m_hLogMutex, 10000);
+
+ if (dwWaitRes == WAIT_OBJECT_0)
+ {
FILE *f;
if ((f=_tfopen(m_strLogPath, _T("a"))) != NULL)
{
- _fputts(buf, f);
-
+ _fputts(msg, f);
+
// prune the log if it gets too big
if (ftell(f) >= (int)m_nMaxSize)
Prune(f);
else
fclose(f);
}
+ ReleaseMutex(m_hLogMutex);
}
}
sWriteString.Format(_T("%s %s %s %ld %s"),JobID, ProcessID, Event, ecode, CIndex);
Write(sWriteString);
-
}
+/**
+ * @brief Prune log file if it exceeds max given size.
+ */
void CLogFile::Prune(FILE *f)
{
TCHAR buf[8196] = {0};
MoveFile(tempfile,m_strLogPath);
}
}
+
+/**
+ * @brief Return message prefix for given loglevel.
+ */
+CString CLogFile::GetPrefix(UINT level) const
+{
+ CString str;
+ switch (level)
+ {
+ case LOGLEVEL::LERROR:
+ str = _T("ERROR: ");
+ break;
+ case LOGLEVEL::LWARNING:
+ str = _T("WARNING: ");
+ break;
+ case LOGLEVEL::LNOTICE:
+ str = _T("NOTICE: ");
+ break;
+ case LOGLEVEL::LMSG:
+ break;
+ case LOGLEVEL::LCODEFLOW:
+ str = _T("FLOW: ");
+ break;
+ case LOGLEVEL::LCOMPAREDATA:
+ str = _T("COMPARE: ");
+ break;
+ default:
+ break;
+ }
+ return str;
+}
-// LogFile.h: interface for the CLogFile class.
-//
-//////////////////////////////////////////////////////////////////////
+/**
+ * @file LogFile.h
+ *
+ * @brief Declaration file for CLogFile
+ *
+ */
+// RCS ID line follows -- this is updated by CVS
+// $Id$
#if !defined(AFX_LOGFILE_H__803A3641_FE03_11D0_95CD_444553540000__INCLUDED_)
#define AFX_LOGFILE_H__803A3641_FE03_11D0_95CD_444553540000__INCLUDED_
-#if _MSC_VER >= 1000
-#pragma once
-#endif // _MSC_VER >= 1000
-
+/**
+ * @brief Messagelevels for log writing.
+ */
+namespace LOGLEVEL
+{
+ enum
+ {
+ LALL = -1, /**< All messages written */
+ LERROR = 0x1, /**< Error messages */
+ LWARNING = 0x2, /**< Warning messages */
+ LNOTICE = 0x4, /**< Important messages */
+ LMSG = 0x8, /**< Normal messages */
+ LCODEFLOW = 0x10, /**< Code flow messages */
+ LCOMPAREDATA = 0x20,
+ };
+};
+/**
+ * @brief Class for writing log files.
+ *
+ * Allows setting masks and levels for messages. They are used for
+ * filtering messages written to log. For example usually its not
+ * needed to see all informal messages, but errors are always good
+ * to log. For simpler usage, default is that all messages are written
+ * and functions with take just message in are provided.
+ * @note User can easily define more levels, just add new constant to
+ * namespace LOGLEVEL above, and possibly prefix to GetPrefix(UINT level).
+ */
class CLogFile
{
public:
+ CLogFile(LPCTSTR szLogName = NULL, LPCTSTR szLogPath = NULL,
+ BOOL bDeleteExisting = FALSE);
+ virtual ~CLogFile();
- CLogFile(LPCTSTR szLogName, LPCTSTR szLogPath = NULL, BOOL bDeleteExisting = FALSE);
+ CString SetFile(CString strFile, CString strPath = _T(""),
+ BOOL bDelExisting = FALSE);
+ void EnableLogging(BOOL bEnable);
+ UINT GetDefaultLevel() const;
+ void SetDefaultLevel(UINT logLevel);
+ UINT GetMaskLevel() const;
+ void SetMaskLevel(UINT maskLevel);
void Write(LPCTSTR pszFormat, ...);
void Write(DWORD idFormatString, ...);
+ void Write(UINT level, LPCTSTR pszFormat, ...);
+ void Write(UINT level, DWORD idFormatString, ...);
// overloaded Write Function to map to Write to Error Set code //
void WriteError(CString JobID, CString ProcessID, CString Event, long ecode, CString CIndex);
- virtual ~CLogFile();
void SetMaxLogSize(DWORD dwMax) { m_nMaxSize = dwMax; }
CString GetPath() const { return m_strLogPath; }
- void EnableLogging(BOOL enable) { m_bEnabled = enable; }
protected:
void Prune(FILE *f);
+ CString GetPrefix(UINT level) const;
+ void Write(CString msg);
+private:
+ HANDLE m_hLogMutex;
DWORD m_nMaxSize;
BOOL m_bEnabled;
CString m_strLogPath;
-
+ UINT m_nDefaultLevel;
+ UINT m_nMaskLevel;
};
#include "diffwrapper.h"
#include "diff.h"
#include "FileTransform.h"
+#include "LogFile.h"
extern int recursive;
+extern CLogFile gLog;
CDiffWrapper::CDiffWrapper()
{
{
LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
strFile1Temp, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ strFile1Temp, GetSysError(GetLastError()));
}
strFile1Temp.Empty();
}
{
LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
strFile2Temp, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ strFile2Temp, GetSysError(GetLastError()));
}
strFile2Temp.Empty();
}
static char THIS_FILE[] = __FILE__;
#endif
+extern CLogFile gLog;
+
// Prompt user to confirm a multiple item copy
static BOOL ConfirmMultipleCopy(int count, int total)
{
{
sText += actionList.errors.RemoveHead() + _T("\r\n\r\n");
}
+ gLog.Write(LOGLEVEL::LERROR, sText);
OutputBox(sTitle, sText);
}
}
m_statusCursor = new CustomStatusCursor(0, IDC_APPSTARTING, LoadResString(IDS_STATUS_RESCANNING));
- gLog.Write(_T("Starting directory scan:\r\n\tLeft: %s\r\n\tRight: %s\r\n"),
+ gLog.Write(LOGLEVEL::LNOTICE, _T("Starting directory scan:\n\tLeft: %s\n\tRight: %s\n"),
m_pCtxt->m_strLeft, m_pCtxt->m_strRight);
pf->clearStatus();
pf->ShowProcessingBar(TRUE);
*/
void CDirDoc::CompareReady()
{
- gLog.Write(_T("Directory scan complete\r\n"));
+ gLog.Write(LOGLEVEL::LNOTICE, _T("Directory scan complete\n"));
// finish the cursor (the hourglass/pointer combo) we had open during display
delete m_statusCursor;
*/
void CDirDoc::AbortCurrentScan()
{
+ gLog.Write(LOGLEVEL::LNOTICE, _T("Dircompare aborted!"));
m_diffThread.Abort();
}
{
m_strLeftDesc = strLeftDesc;
m_strRightDesc = strRightDesc;
-}
\ No newline at end of file
+}
+
// based on file date, it could be done here, with the same caveat
// as above
- gLog.Write(_T("Comparing: n0=%s, n1=%s, d0=%s, d1=%s")
+ gLog.Write(LOGLEVEL::LCOMPAREDATA, _T("Comparing: n0=%s, n1=%s, d0=%s, d1=%s")
, lent.name, rent.name, sLeftDir, sRightDir);
CString filepath1 = paths_ConcatPath(sLeftDir, lent.name);
CString filepath2 = paths_ConcatPath(sRightDir, rent.name);
name = rent->name;
rattrs = rent->attrs;
}
- gLog.Write(_T("name=<%s>, leftdir=<%s>, rightdir=<%s>, code=%d")
+ gLog.Write(LOGLEVEL::LCOMPAREDATA,_T("name=<%s>, leftdir=<%s>, rightdir=<%s>, code=%d")
, (LPCTSTR)name, (LPCTSTR)leftdir, (LPCTSTR)rightdir, code);
pCtxt->AddDiff(name, sDir, leftdir, rightdir
, lmtime, rmtime, lctime, rctime, lsize, rsize, code, lattrs, rattrs
static char THIS_FILE[] = __FILE__;
#endif
+extern CLogFile gLog;
CMainFrame *mf = NULL;
-CLogFile gLog(_T("WinMerge.log"), NULL, TRUE);
// add a
static void add_regexp PARAMS((struct regexp_list **, char const*));
*/
CMainFrame::CMainFrame()
{
+#if defined (_DEBUG) || defined (ENABLE_LOG)
+ gLog.SetFile(_T("WinMerge.log"));
+ gLog.EnableLogging(TRUE);
+ // Not interested about compare data (very noisy!)
+ gLog.SetMaskLevel(LOGLEVEL::LALL & ~LOGLEVEL::LCOMPAREDATA);
+#endif
+
+// Only log errors and warnings for release!
+//#ifndef _DEBUG
+// gLog.SetMaskLevel(LOGLEVEL::LERROR | LOGLEVEL::LWARNING);
+//#endif
+
m_bFontSpecified=FALSE;
m_strSaveAsPath = _T("");
m_bFirstTime = TRUE;
break;
if (strLeft.Find(path) == 0)
{
- DeleteFile(strLeft);
+ if (!::DeleteFile(strLeft))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ strLeft, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ strLeft, GetSysError(GetLastError()));
+ }
}
strLeft.Delete(0, strLeft.ReverseFind('\\'));
int dot = strLeft.ReverseFind('.');
break;;
if (strRight.Find(path) == 0)
{
- DeleteFile(strRight);
+ if (!::DeleteFile(strRight))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ strRight, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ strRight, GetSysError(GetLastError()));
+ }
}
strRight.Delete(0, strRight.ReverseFind('\\'));
int dot = strRight.ReverseFind('.');
CDiffContext *pCtxt = new CDiffContext(strLeft, strRight);
if (pCtxt != NULL)
{
+ gLog.Write(LOGLEVEL::LNOTICE, _T("Open dirs: Left: %s\n\tRight: %s."),
+ strLeft, strRight);
+
pDirDoc->SetReadOnly(TRUE, bROLeft);
pDirDoc->SetReadOnly(FALSE, bRORight);
pDirDoc->SetRecursive(bRecurse);
if (files_isFileReadOnly(strRight))
bRORight = TRUE;
+ gLog.Write(LOGLEVEL::LNOTICE, _T("Open files: Left: %s\n\tRight: %s."),
+ strLeft, strRight);
+
ShowMergeDoc(pDirDoc, strLeft, strRight, bROLeft, bRORight,
cpleft, cpright, &infoUnpacker);
}
files[1] = files[0];
}
+ gLog.Write(LOGLEVEL::LNOTICE, _T("D&D open: Left: %s\n\tRight: %s."),
+ files[0], files[1]);
+
DoFileOpen(files[0], files[1], FFILEOPEN_NONE, FFILEOPEN_NONE, ctrlKey);
}
ShowMergeDoc(pDirDoc, _T(""), _T(""), FALSE, FALSE, 0, 0);
}
-
static char THIS_FILE[] = __FILE__;
#endif
+extern CLogFile gLog;
/**
* @brief EOL types
// delete the file that unpacking may have created
if (_tcscmp(pszFileNameInit, pszFileName) != 0)
- ::DeleteFile(pszFileName);
+ if (!::DeleteFile(pszFileName))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ pszFileName, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ pszFileName, GetSysError(GetLastError()));
+ }
return nRetVal;
}
infoUnpacker->subcode = unpackerSubcode;
if (!FileTransform_Packing(csTempFileName, *infoUnpacker))
{
- ::DeleteFile(sIntermediateFilename);
+ if (!::DeleteFile(sIntermediateFilename))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError()));
+ }
// returns now, don't overwrite the original file
return SAVE_PACK_FAILED;
}
// the temp filename may have changed during packing
if (csTempFileName != sIntermediateFilename)
{
- ::DeleteFile(sIntermediateFilename);
+ if (!::DeleteFile(sIntermediateFilename))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError()));
+ }
sIntermediateFilename = csTempFileName;
}
// Write tempfile over original file
if (::CopyFile(sIntermediateFilename, pszFileName, FALSE))
{
- ::DeleteFile(sIntermediateFilename);
+ if (!::DeleteFile(sIntermediateFilename))
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ sIntermediateFilename, GetSysError(GetLastError()));
+ }
if (bClearModifiedFlag)
{
SetModified(FALSE);
if (::DeleteFile(m_strTempLeftFile))
m_strTempLeftFile = _T("");
else
- LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s")
- , m_strTempLeftFile, GetSysError(GetLastError())));
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ m_strTempLeftFile, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ m_strTempLeftFile, GetSysError(GetLastError()));
+ }
}
if (!m_strTempRightFile.IsEmpty())
{
if (::DeleteFile(m_strTempRightFile))
m_strTempRightFile = _T("");
else
- LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s")
- , m_strTempRightFile, GetSysError(GetLastError())));
+ {
+ LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
+ m_strTempRightFile, GetSysError(GetLastError())));
+ gLog.Write(LOGLEVEL::LERROR, _T("DeleteFile(%s) failed: %s"),
+ m_strTempRightFile, GetSysError(GetLastError()));
+ }
}
}
pf->GetHeaderInterface()->SetActive(nPane, bActivate);
}
-
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
+#include "LogFile.h"
+
+// Logging
+CLogFile gLog;
// fix any nonascii characters
// which got sign-extended to become negative integers
return CF_TEXT;
#endif // _UNICODE
}
+
Src: DirActions.cpp
PATCH: [ 889115 ] Delete transformed temp files in file compare
Src: DiffWrapper.cpp
+ PATCH: [ 889334 ] Improve and enable logging
+ Logging is now enabled for debug builds, written to $temp/WinMerge.log
+ Common: LogFile.cpp LogFile.h
+ Src: DiffWrapper.cpp DirActions.cpp DirDoc.cpp DirScan.cpp
+ MainFrm.cpp MergeDoc.cpp StdAfx.cpp
2004-02-01 Perry
Cosmetic: fix copy&paste error in UniFile.cpp comment.