Add component-level filtering to wxLog.

Each log message is now associated with its component, "wx" by default for
messages generated by wxWidgets and wxLOG_COMPONENT in general (which is empty
by default). Each component may have its own log level and they are
hierarchical allowing fine configuration of what exactly is logged.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61414 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2009-07-12 14:56:23 +00:00
parent fbbde24964
commit c602c59b6e
5 changed files with 293 additions and 58 deletions

View File

@@ -120,10 +120,58 @@ classes are. Some of advantages in using wxWidgets log functions are:
about data file writing error.
@section overview_log_enable Log Messages Selection
By default, most log messages are enabled. In particular, this means that
errors logged by wxWidgets code itself (e.g. when it fails to perform some
operation, for instance wxFile::Open() logs an error when it fails to open a
file) will be processed and shown to the user. To disable the logging entirely
you can use wxLog::EnableLogging() method or, more usually, wxLogNull class
which temporarily disables logging and restores it back to the original setting
when it is destroyed.
To limit logging to important messages only, you may use wxLog::SetLogLevel()
with e.g. wxLOG_Warning value -- this will completely disable all logging
messages with the severity less than warnings, so wxLogMessage() output won't
be shown to the user any more.
Moreover, the log level can be set separately for different log components.
Before showing how this can be useful, let us explain what log components are:
they are simply arbitrary strings identifying the component, or module, which
generated the message. They are hierarchical in the sense that "foo/bar/baz"
component is supposed to be a child of "foo". And all components are children
of the unnamed root component.
By default, all messages logged by wxWidgets originate from "wx" component or
one of its subcomponents such as "wx/net/ftp", while the messages logged by
your own code are assigned empty log component. To change this, you need to
define @c wxLOG_COMPONENT to a string uniquely identifying each component, e.g.
you could give it the value "MyProgram" by default and re-define it as
"MyProgram/DB" in the module working with the database and "MyProgram/DB/Trans"
in its part managing the transactions. Then you could use
wxLog::SetComponentLevel() in the following ways:
@code
// disable all database error messages, everybody knows databases never
// fail anyhow
wxLog::SetComponentLevel("MyProgram/DB", wxLOG_FatalError);
// but enable tracing for the transactions as somehow our changes don't
// get committed sometimes
wxLog::SetComponentLevel("MyProgram/DB/Trans", wxLOG_Trace);
// also enable tracing messages from wxWidgets dynamic module loading
// mechanism
wxLog::SetComponentLevel("wx/base/module", wxLOG_Trace);
@endcode
Notice that the log level set explicitly for the transactions code overrides
the log level of the parent component but that all other database code
subcomponents inherit its setting by default and so won't generate any log
messages at all.
@section overview_log_targets Log Targets
After having enumerated all the functions which are normally used to log the
messages, and why would you want to use them we now describe how all this
messages, and why would you want to use them, we now describe how all this
works.
wxWidgets has the notion of a <em>log target</em>: it is just a class deriving

View File

@@ -78,6 +78,19 @@ typedef unsigned long wxLogLevel;
#endif
#endif // wxUSE_LOG_TRACE
// wxLOG_COMPONENT identifies the component which generated the log record and
// can be #define'd to a user-defined value when compiling the user code to use
// component-based filtering (see wxLog::SetComponentLevel())
#ifndef wxLOG_COMPONENT
// this is a variable and not a macro in order to allow the user code to
// just #define wxLOG_COMPONENT without #undef'ining it first
extern WXDLLIMPEXP_DATA_BASE(const char *) wxLOG_COMPONENT;
#ifdef WXBUILDING
#define wxLOG_COMPONENT "wx"
#endif
#endif
// ----------------------------------------------------------------------------
// forward declarations
// ----------------------------------------------------------------------------
@@ -133,18 +146,20 @@ public:
// default ctor creates an uninitialized object
wxLogRecordInfo()
{
memset(this, 0, sizeof(this));
memset(this, 0, sizeof(*this));
}
// normal ctor, used by wxLogger specifies the location of the log
// statement; its time stamp and thread id are set up here
wxLogRecordInfo(const char *filename_,
int line_,
const char *func_)
const char *func_,
const char *component_)
{
filename = filename_;
func = func_;
line = line_;
component = component_;
timestamp = time(NULL);
@@ -188,6 +203,10 @@ public:
// if the compiler doesn't support __FUNCTION__)
const char *func;
// the name of the component which generated this message, may be NULL if
// not set (i.e. wxLOG_COMPONENT not defined)
const char *component;
// time of record generation
time_t timestamp;
@@ -250,7 +269,7 @@ public:
private:
void Copy(const wxLogRecordInfo& other)
{
memcpy(this, &other, sizeof(wxLogRecordInfo));
memcpy(this, &other, sizeof(*this));
if ( other.m_data )
m_data = new ExtraData(*other.m_data);
}
@@ -285,20 +304,61 @@ public:
virtual ~wxLog();
// these functions allow to completely disable all log messages
// log messages selection
// ----------------------
// these functions allow to completely disable all log messages or disable
// log messages at level less important than specified
// is logging enabled at all now?
static bool IsEnabled() { return ms_doLog; }
// is logging at this level enabled?
static bool IsLevelEnabled(wxLogLevel level)
{ return IsEnabled() && level <= ms_logLevel; }
// change the flag state, return the previous one
static bool EnableLogging(bool doIt = true)
{ bool doLogOld = ms_doLog; ms_doLog = doIt; return doLogOld; }
// return the current global log level
static wxLogLevel GetLogLevel() { return ms_logLevel; }
// set global log level: messages with level > logLevel will not be logged
static void SetLogLevel(wxLogLevel logLevel) { ms_logLevel = logLevel; }
// set the log level for the given component
static void SetComponentLevel(const wxString& component, wxLogLevel level);
// return the effective log level for this component, falling back to
// parent component and to the default global log level if necessary
//
// NB: component argument is passed by value and not const reference in an
// attempt to encourage compiler to avoid an extra copy: as we modify
// the component internally, we'd create one anyhow and like this it
// can be avoided if the string is a temporary anyhow
static wxLogLevel GetComponentLevel(wxString component);
// is logging of messages from this component enabled at this level?
//
// usually always called with wxLOG_COMPONENT as second argument
static bool IsLevelEnabled(wxLogLevel level, wxString component)
{
return IsEnabled() && level <= GetComponentLevel(component);
}
// enable/disable messages at wxLOG_Verbose level (only relevant if the
// current log level is greater or equal to it)
//
// notice that verbose mode can be activated by the standard command-line
// '--verbose' option
static void SetVerbose(bool bVerbose = true) { ms_bVerbose = bVerbose; }
// check if verbose messages are enabled
static bool GetVerbose() { return ms_bVerbose; }
// message buffering
// -----------------
// flush shows all messages if they're not logged immediately (FILE
// and iostream logs don't need it, but wxGuiLog does to avoid showing
@@ -332,14 +392,6 @@ public:
// must be called for each Suspend()!
static void Resume() { ms_suspendCount--; }
// functions controlling the default wxLog behaviour
// verbose mode is activated by standard command-line '--verbose'
// option
static void SetVerbose(bool bVerbose = true) { ms_bVerbose = bVerbose; }
// Set log level. Log messages with level > logLevel will not be logged.
static void SetLogLevel(wxLogLevel logLevel) { ms_logLevel = logLevel; }
// should GetActiveTarget() try to create a new log object if the
// current is NULL?
static void DontCreateOnDemand();
@@ -377,17 +429,9 @@ public:
static void DisableTimestamp() { SetTimestamp(wxEmptyString); }
// accessors
// gets the verbose status
static bool GetVerbose() { return ms_bVerbose; }
// is this trace mask in the list?
static bool IsAllowedTraceMask(const wxString& mask);
// return the current loglevel limit
static wxLogLevel GetLogLevel() { return ms_logLevel; }
// get the current timestamp format string (maybe empty)
static const wxString& GetTimestamp() { return ms_timestamp; }
@@ -742,9 +786,10 @@ public:
wxLogger(wxLogLevel level,
const char *filename,
int line,
const char *func)
const char *func,
const char *component)
: m_level(level),
m_info(filename, line, func)
m_info(filename, line, func, component)
{
}
@@ -778,7 +823,8 @@ public:
void LogV(const wxString& format, va_list argptr)
{
// remember that fatal errors can't be disabled
if ( m_level == wxLOG_FatalError || wxLog::IsLevelEnabled(m_level) )
if ( m_level == wxLOG_FatalError ||
wxLog::IsLevelEnabled(m_level, m_info.component) )
DoCallOnLog(format, argptr);
}
@@ -980,7 +1026,7 @@ private:
void DoLogAtLevel(wxLogLevel level, const wxChar *format, ...)
{
if ( !wxLog::IsLevelEnabled(level) )
if ( !wxLog::IsLevelEnabled(level, m_info.component) )
return;
va_list argptr;
@@ -1049,7 +1095,7 @@ private:
void DoLogAtLevelUtf8(wxLogLevel level, const char *format, ...)
{
if ( !wxLog::IsLevelEnabled(level) )
if ( !wxLog::IsLevelEnabled(level, m_info.component) )
return;
va_list argptr;
@@ -1155,7 +1201,7 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
// creates wxLogger object for the current location
#define wxMAKE_LOGGER(level) \
wxLogger(wxLOG_##level, __FILE__, __LINE__, __WXFUNCTION__)
wxLogger(wxLOG_##level, __FILE__, __LINE__, __WXFUNCTION__, wxLOG_COMPONENT)
// this macro generates the expression which logs whatever follows it in
// parentheses at the level specified as argument
@@ -1188,7 +1234,7 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
// easily fixed by adding curly braces around wxLogError() and at least
// the code still does do the right thing.
#define wxDO_LOG_IF_ENABLED(level) \
if ( !wxLog::IsLevelEnabled(wxLOG_##level) ) \
if ( !wxLog::IsLevelEnabled(wxLOG_##level, wxLOG_COMPONENT) ) \
{} \
else \
wxDO_LOG(level)
@@ -1208,12 +1254,14 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
// this one is special as it only logs if we're in verbose mode
#define wxLogVerbose \
if ( !(wxLog::IsLevelEnabled(wxLOG_Info) && wxLog::GetVerbose()) ) \
if ( !(wxLog::IsLevelEnabled(wxLOG_Info, wxLOG_COMPONENT) && \
wxLog::GetVerbose()) ) \
{} \
else \
wxDO_LOG(Info)
#define wxVLogVerbose(format, argptr) \
if ( !(wxLog::IsLevelEnabled(wxLOG_Info) && wxLog::GetVerbose()) ) \
if ( !(wxLog::IsLevelEnabled(wxLOG_Info, wxLOG_COMPONENT) && \
wxLog::GetVerbose()) ) \
{} \
else \
wxDO_LOGV(Info, format, argptr)
@@ -1230,7 +1278,7 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
// always evaluated, unlike for the other log functions
#define wxLogGeneric wxMAKE_LOGGER(Max).LogAtLevel
#define wxVLogGeneric(level, format, argptr) \
if ( !wxLog::IsLevelEnabled(wxLOG_##level) ) \
if ( !wxLog::IsLevelEnabled(wxLOG_##level, wxLOG_COMPONENT) ) \
{} \
else \
wxDO_LOGV(level, format, argptr)
@@ -1242,7 +1290,7 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
#define wxLOG_KEY_SYS_ERROR_CODE "wx.sys_error"
#define wxLogSysError \
if ( !wxLog::IsLevelEnabled(wxLOG_Error) ) \
if ( !wxLog::IsLevelEnabled(wxLOG_Error, wxLOG_COMPONENT) ) \
{} \
else \
wxMAKE_LOGGER(Error).MaybeStore(wxLOG_KEY_SYS_ERROR_CODE).Log
@@ -1259,7 +1307,7 @@ WXDLLIMPEXP_BASE const wxChar* wxSysErrorMsg(unsigned long nErrCode = 0);
#define wxLOG_KEY_FRAME "wx.frame"
#define wxLogStatus \
if ( !wxLog::IsLevelEnabled(wxLOG_Status) ) \
if ( !wxLog::IsLevelEnabled(wxLOG_Status, wxLOG_COMPONENT) ) \
{} \
else \
wxMAKE_LOGGER(Status).MaybeStore(wxLOG_KEY_FRAME).Log
@@ -1379,7 +1427,7 @@ public:
#if wxUSE_LOG_TRACE
#define wxLogTrace \
if ( !wxLog::IsLevelEnabled(wxLOG_Trace) ) \
if ( !wxLog::IsLevelEnabled(wxLOG_Trace, wxLOG_COMPONENT) ) \
{} \
else \
wxMAKE_LOGGER(Trace).LogTrace

View File

@@ -828,11 +828,14 @@ public:
Returns true if logging at this level is enabled.
This function only returns @true if logging is globally enabled and if
this level is less than or equal to the global log level value.
@a level is less than or equal to the maximal log level enabled for the
given @a component.
@see IsEnabled(), SetLogLevel(), GetLogLevel()
@see IsEnabled(), SetLogLevel(), GetLogLevel(), SetComponentLevel()
@since 2.9.1
*/
static bool IsLevelEnabled(wxLogLevel level);
static bool IsLevelEnabled(wxLogLevel level, wxString component);
/**
Remove the @a mask from the list of allowed masks for
@@ -858,9 +861,33 @@ public:
*/
static wxLog* SetActiveTarget(wxLog* logtarget);
/**
Sets the log level for the given component.
For example, to disable all but error messages from wxWidgets network
classes you may use
@code
wxLog::SetComponentLevel("wx/net", wxLOG_Error);
@endcode
SetLogLevel() may be used to set the global log level.
@param component
Non-empty component name, possibly using slashes (@c /) to separate
it into several parts.
@param level
Maximal level of log messages from this component which will be
handled instead of being simply discarded.
@since 2.9.1
*/
static void SetComponentLevel(const wxString& component, wxLogLevel level);
/**
Specifies that log messages with level greater (numerically) than
@a logLevel should be ignored and not sent to the active log target.
@see SetComponentLevel()
*/
static void SetLogLevel(wxLogLevel logLevel);

View File

@@ -63,6 +63,9 @@
#include "wx/msw/private.h" // includes windows.h
#endif
#undef wxLOG_COMPONENT
const char *wxLOG_COMPONENT = "";
#if wxUSE_THREADS
// define static functions providing access to the critical sections we use
@@ -85,6 +88,13 @@ static inline wxCriticalSection& GetPreviousLogCS()
return s_csPrev;
}
static inline wxCriticalSection& GetLevelsCS()
{
static wxCriticalSection s_csLevels;
return s_csLevels;
}
#endif // wxUSE_THREADS
// ----------------------------------------------------------------------------
@@ -130,6 +140,12 @@ struct PreviousLogInfo
PreviousLogInfo gs_prevLog;
// map containing all components for which log level was explicitly set
//
// NB: all accesses to it must be protected by GetLevelsCS() critical section
wxStringToNumHashMap gs_componentLevels;
} // anonymous namespace
// ============================================================================
@@ -437,6 +453,47 @@ void wxLog::DoCreateOnDemand()
ms_bAutoCreate = true;
}
// ----------------------------------------------------------------------------
// wxLog components levels
// ----------------------------------------------------------------------------
/* static */
void wxLog::SetComponentLevel(const wxString& component, wxLogLevel level)
{
if ( component.empty() )
{
SetLogLevel(level);
}
else
{
wxCRIT_SECT_LOCKER(lock, GetLevelsCS());
gs_componentLevels[component] = level;
}
}
/* static */
wxLogLevel wxLog::GetComponentLevel(wxString component)
{
wxCRIT_SECT_LOCKER(lock, GetLevelsCS());
while ( !component.empty() )
{
wxStringToNumHashMap::const_iterator
it = gs_componentLevels.find(component);
if ( it != gs_componentLevels.end() )
return static_cast<wxLogLevel>(it->second);
component = component.BeforeLast('/');
}
return GetLogLevel();
}
// ----------------------------------------------------------------------------
// wxLog trace masks
// ----------------------------------------------------------------------------
void wxLog::AddTraceMask(const wxString& str)
{
wxCRIT_SECT_LOCKER(lock, GetTraceMaskCS());
@@ -460,6 +517,25 @@ void wxLog::ClearTraceMasks()
ms_aTraceMasks.Clear();
}
/*static*/ bool wxLog::IsAllowedTraceMask(const wxString& mask)
{
wxCRIT_SECT_LOCKER(lock, GetTraceMaskCS());
for ( wxArrayString::iterator it = ms_aTraceMasks.begin(),
en = ms_aTraceMasks.end();
it != en; ++it )
{
if ( *it == mask)
return true;
}
return false;
}
// ----------------------------------------------------------------------------
// wxLog miscellaneous other methods
// ----------------------------------------------------------------------------
void wxLog::TimeStamp(wxString *str)
{
#if wxUSE_DATETIME
@@ -484,21 +560,6 @@ void wxLog::Flush()
LogLastRepeatIfNeeded();
}
/*static*/ bool wxLog::IsAllowedTraceMask(const wxString& mask)
{
wxCRIT_SECT_LOCKER(lock, GetTraceMaskCS());
for ( wxArrayString::iterator it = ms_aTraceMasks.begin(),
en = ms_aTraceMasks.end();
it != en; ++it )
{
if ( *it == mask)
return true;
}
return false;
}
// ----------------------------------------------------------------------------
// wxLogBuffer implementation
// ----------------------------------------------------------------------------

View File

@@ -31,6 +31,9 @@
#endif // VC++ 7+
#endif // WXWIN_COMPATIBILITY_2_8
// all calls to wxLogXXX() functions from this file will use this log component
#define wxLOG_COMPONENT "test"
// ----------------------------------------------------------------------------
// test loggers
// ----------------------------------------------------------------------------
@@ -42,19 +45,28 @@ class TestLogBase : public wxLog
public:
TestLogBase() { }
wxString GetLog(wxLogLevel level) const
const wxString& GetLog(wxLogLevel level) const
{
return m_logs[level];
}
const wxLogRecordInfo& GetInfo(wxLogLevel level) const
{
return m_logsInfo[level];
}
void Clear()
{
for ( unsigned n = 0; n < WXSIZEOF(m_logs); n++ )
{
m_logs[n].clear();
m_logsInfo[n] = wxLogRecordInfo();
}
}
protected:
wxString m_logs[wxLOG_Trace + 1];
wxLogRecordInfo m_logsInfo[wxLOG_Trace + 1];
wxDECLARE_NO_COPY_CLASS(TestLogBase);
};
@@ -68,9 +80,10 @@ public:
protected:
virtual void DoLogRecord(wxLogLevel level,
const wxString& msg,
const wxLogRecordInfo& WXUNUSED(info))
const wxLogRecordInfo& info)
{
m_logs[level] = msg;
m_logsInfo[level] = info;
}
private:
@@ -147,6 +160,7 @@ private:
CPPUNIT_TEST_SUITE( LogTestCase );
CPPUNIT_TEST( Functions );
CPPUNIT_TEST( Null );
CPPUNIT_TEST( Component );
#if wxDEBUG_LEVEL
CPPUNIT_TEST( Trace );
#endif // wxDEBUG_LEVEL
@@ -158,6 +172,7 @@ private:
void Functions();
void Null();
void Component();
#if wxDEBUG_LEVEL
void Trace();
#endif // wxDEBUG_LEVEL
@@ -220,6 +235,42 @@ void LogTestCase::Null()
CPPUNIT_ASSERT_EQUAL( "Important warning", m_log->GetLog(wxLOG_Warning) );
}
void LogTestCase::Component()
{
wxLogMessage("Message");
CPPUNIT_ASSERT_EQUAL( wxLOG_COMPONENT,
m_log->GetInfo(wxLOG_Message).component );
// completely disable logging for this component
wxLog::SetComponentLevel("test/ignore", wxLOG_FatalError);
// but enable it for one of its subcomponents
wxLog::SetComponentLevel("test/ignore/not", wxLOG_Max);
#undef wxLOG_COMPONENT
#define wxLOG_COMPONENT "test/ignore"
// this shouldn't be output as this component is ignored
wxLogError("Error");
CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Error) );
// and so are its subcomponents
#undef wxLOG_COMPONENT
#define wxLOG_COMPONENT "test/ignore/sub/subsub"
wxLogError("Error");
CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Error) );
// but one subcomponent is not
#undef wxLOG_COMPONENT
#define wxLOG_COMPONENT "test/ignore/not"
wxLogError("Error");
CPPUNIT_ASSERT_EQUAL( "Error", m_log->GetLog(wxLOG_Error) );
// restore the original value
#undef wxLOG_COMPONENT
#define wxLOG_COMPONENT "test"
}
#if wxDEBUG_LEVEL
void LogTestCase::Trace()