From c602c59b6e623d7775c16ce6412b64b34dc5dd94 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Jul 2009 14:56:23 +0000 Subject: [PATCH] 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 --- docs/doxygen/overviews/log.h | 50 +++++++++++++++++- include/wx/log.h | 122 ++++++++++++++++++++++++++++++------------- interface/wx/log.h | 33 ++++++++++-- src/common/log.cpp | 91 ++++++++++++++++++++++++++------ tests/log/logtest.cpp | 55 ++++++++++++++++++- 5 files changed, 293 insertions(+), 58 deletions(-) diff --git a/docs/doxygen/overviews/log.h b/docs/doxygen/overviews/log.h index d8cb23e..4042ec2 100644 --- a/docs/doxygen/overviews/log.h +++ b/docs/doxygen/overviews/log.h @@ -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 log target: it is just a class deriving diff --git a/include/wx/log.h b/include/wx/log.h index 34168be..73a8e60 100644 --- a/include/wx/log.h +++ b/include/wx/log.h @@ -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 diff --git a/interface/wx/log.h b/interface/wx/log.h index 42ef240..49a9f10 100644 --- a/interface/wx/log.h +++ b/interface/wx/log.h @@ -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 @@ -859,8 +862,32 @@ 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); diff --git a/src/common/log.cpp b/src/common/log.cpp index e0686ce..1b599cc 100644 --- a/src/common/log.cpp +++ b/src/common/log.cpp @@ -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(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 // ---------------------------------------------------------------------------- diff --git a/tests/log/logtest.cpp b/tests/log/logtest.cpp index f1dbb8c..0334bc6 100644 --- a/tests/log/logtest.cpp +++ b/tests/log/logtest.cpp @@ -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() -- 2.7.4