diff --git a/src/App/Application.cpp b/src/App/Application.cpp index e40d8b17a9..d8c6b70efb 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -391,6 +391,8 @@ bool Application::closeDocument(const char* name) if (pos == DocMap.end()) // no such document return false; + Base::ConsoleRefreshDisabler disabler; + // Trigger observers before removing the document from the internal map. // Some observers might rely on this document still being there. signalDeleteDocument(*pos->second); @@ -1417,6 +1419,35 @@ void Application::initConfig(int argc, char ** argv) LoadParameters(); + auto loglevelParam = _pcUserParamMngr->GetGroup("BaseApp/LogLevels"); + const auto &loglevels = loglevelParam->GetIntMap(); + bool hasDefault = false; + for(const auto &v : loglevels) { + if(v.first == "Default") { +#ifndef FC_DEBUG + if(v.second>=0) { + hasDefault = true; + Base::Console().SetDefaultLogLevel(v.second); + } +#endif + }else if(v.first == "DebugDefault") { +#ifdef FC_DEBUG + if(v.second>=0) { + hasDefault = true; + Base::Console().SetDefaultLogLevel(v.second); + } +#endif + }else + *Base::Console().GetLogLevel(v.first.c_str()) = v.second; + } + if(!hasDefault) { +#ifdef FC_DEBUG + loglevelParam->SetInt("DebugDefault",Base::Console().LogLevel(-1)); +#else + loglevelParam->SetInt("Default",Base::Console().LogLevel(-1)); +#endif + } + // Set application tmp. directory mConfig["AppTempPath"] = Base::FileInfo::getTempPath(); std::string tmpPath = _pcUserParamMngr->GetGroup("BaseApp/Preferences/General")->GetASCII("TempPath"); diff --git a/src/App/Application.h b/src/App/Application.h index 00bc677b27..85dc9952f2 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -321,6 +321,9 @@ private: static PyObject* sRemoveDocObserver (PyObject *self,PyObject *args,PyObject *kwd); static PyObject* sTranslateUnit (PyObject *self,PyObject *args,PyObject *kwd); + static PyObject *sSetLogLevel (PyObject *self,PyObject *args,PyObject *kwd); + static PyObject *sGetLogLevel (PyObject *self,PyObject *args,PyObject *kwd); + static PyMethodDef Methods[]; friend class ApplicationObserver; diff --git a/src/App/ApplicationPy.cpp b/src/App/ApplicationPy.cpp index 20df82918a..9f5e8da480 100644 --- a/src/App/ApplicationPy.cpp +++ b/src/App/ApplicationPy.cpp @@ -129,6 +129,11 @@ PyMethodDef Application::Methods[] = { {"removeDocumentObserver", (PyCFunction) Application::sRemoveDocObserver ,1, "removeDocumentObserver() -> None\n\n" "Remove an added document observer."}, + {"setLogLevel", (PyCFunction) Application::sSetLogLevel, 1, + "setLogLevel(tag, level) -- Set the log level for a string tag.\n" + "'level' can either be string 'Log', 'Msg', 'Wrn', 'Error', or an integer value"}, + {"getLogLevel", (PyCFunction) Application::sGetLogLevel, 1, + "getLogLevel(tag) -- Get the log level of a string tag"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -604,3 +609,90 @@ PyObject* Application::sRemoveDocObserver(PyObject * /*self*/, PyObject *args,Py Py_Return; } PY_CATCH; } + +PyObject *Application::sSetLogLevel(PyObject * /*self*/, PyObject *args, PyObject * /*kwd*/) +{ + char *tag; + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "sO", &tag, &pcObj)) + return NULL; + PY_TRY{ + int l; + if (PyString_Check(pcObj)) { + const char *pstr = PyString_AsString(pcObj); + if(strcmp(pstr,"Log") == 0) + l = FC_LOGLEVEL_LOG; + else if(strcmp(pstr,"Warning") == 0) + l = FC_LOGLEVEL_WARN; + else if(strcmp(pstr,"Message") == 0) + l = FC_LOGLEVEL_MSG; + else if(strcmp(pstr,"Error") == 0) + l = FC_LOGLEVEL_ERR; + else if(strcmp(pstr,"Trace") == 0) + l = FC_LOGLEVEL_TRACE; + else if(strcmp(pstr,"Default") == 0) + l = FC_LOGLEVEL_DEFAULT; + else { + Py_Error(Base::BaseExceptionFreeCADError, + "Unknown Log Level (use 'Default', 'Error', 'Warning', 'Message', 'Log', 'Trace' or an integer)"); + return NULL; + } + }else + l = PyLong_AsLong(pcObj); + GetApplication().GetParameterGroupByPath("User parameter:BaseApp/LogLevels")->SetInt(tag,l); + if(strcmp(tag,"Default") == 0) { +#ifndef FC_DEBUG + if(l>=0) Base::Console().SetDefaultLogLevel(l); +#endif + }else if(strcmp(tag,"DebugDefault") == 0) { +#ifdef FC_DEBUG + if(l>=0) Base::Console().SetDefaultLogLevel(l); +#endif + }else + *Base::Console().GetLogLevel(tag) = l; + Py_INCREF(Py_None); + return Py_None; + }PY_CATCH; +} + +PyObject *Application::sGetLogLevel(PyObject * /*self*/, PyObject *args, PyObject * /*kwd*/) +{ + char *tag; + if (!PyArg_ParseTuple(args, "s", &tag)) + return NULL; + + PY_TRY{ + int l = -1; + if(strcmp(tag,"Default")==0) { +#ifdef FC_DEBUG + l = _pcUserParamMngr->GetGroup("BaseApp/LogLevels")->GetInt(tag,-1); +#endif + }else if(strcmp(tag,"DebugDefault")) { +#ifndef FC_DEBUG + l = _pcUserParamMngr->GetGroup("BaseApp/LogLevels")->GetInt(tag,-1); +#endif + }else{ + int *pl = Base::Console().GetLogLevel(tag,false); + l = pl?*pl:-1; + } + // For performance reason, we only output integer value + return Py_BuildValue("i",Base::Console().LogLevel(l)); + + // switch(l) { + // case FC_LOGLEVEL_LOG: + // return Py_BuildValue("s","Log"); + // case FC_LOGLEVEL_WARN: + // return Py_BuildValue("s","Warning"); + // case FC_LOGLEVEL_ERR: + // return Py_BuildValue("s","Error"); + // case FC_LOGLEVEL_MSG: + // return Py_BuildValue("s","Message"); + // case FC_LOGLEVEL_TRACE: + // return Py_BuildValue("s","Trace"); + // default: + // return Py_BuildValue("i",l); + // } + } PY_CATCH; +} + + diff --git a/src/Base/Console.cpp b/src/Base/Console.cpp index 84afb6ab89..5b75a5807a 100644 --- a/src/Base/Console.cpp +++ b/src/Base/Console.cpp @@ -39,6 +39,7 @@ #include "Console.h" #include "Exception.h" #include "PyObjectBase.h" +#include using namespace Base; @@ -50,7 +51,12 @@ using namespace Base; ConsoleSingleton::ConsoleSingleton(void) - :_bVerbose(false) + :_bVerbose(false),_bCanRefresh(true) +#ifdef FC_DEBUG + ,_defaultLogLevel(FC_LOGLEVEL_LOG) +#else + ,_defaultLogLevel(FC_LOGLEVEL_MSG) +#endif { } @@ -354,6 +360,24 @@ ConsoleObserver *ConsoleSingleton::Get(const char *Name) const return 0; } +int *ConsoleSingleton::GetLogLevel(const char *tag, bool create) { + if(!tag) tag = ""; + if(_logLevels.find(tag) != _logLevels.end()) + return &_logLevels[tag]; + if(!create) return 0; + int &ret = _logLevels[tag]; + ret = -1; + return &ret; +} + +void ConsoleSingleton::Refresh() { + if(_bCanRefresh) + QCoreApplication::sendPostedEvents(); +} + +void ConsoleSingleton::EnableRefresh(bool enable) { + _bCanRefresh = enable; +} //************************************************************************** // Singleton stuff @@ -781,3 +805,24 @@ int RedirectStdError::sync() } return 0; } + +//--------------------------------------------------------- +std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int line) { + static FC_TIME_POINT s_tstart; + static bool s_timing = false; + if(print_time) { + if(!s_timing) { + s_timing = true; + _FC_TIME_INIT(s_tstart); + } + auto tnow = std::chrono::FC_TIME_CLOCK::now(); + auto d = std::chrono::duration_cast(tnow-s_tstart); + str << d.count() << ' '; + } + if(print_tag) str << '<' << tag << "> "; + if(print_src) { + const char *_f = std::strrchr(src, '/'); + str << (_f?_f+1:src)<<"("< #include - //#pragma warning(disable: 4786) // specifier longer then 255 chars #include #include +#include #include +#include +#include +#include //************************************************************************** // Loging levels @@ -49,6 +52,382 @@ # undef FC_LOGUPDATECHAIN #endif +///////////////////////////////////////////////////////////////////////////////////// + +/** \page LogLevelPage Tag based log helpers + * Simple tag based log and timing helper macros and functions. + * + * \section Motivation + * + * FreeCAD Base::Console() is capable of outputing to different targets, and has + * some basic enable/disable control of different types of logs. There is, + * however, no easy to use logging facility for various FC modules. This set of + * helper macros and function is aimed to provide a unified logging (and timing) + * interface. The interface is mainly designed for C++ code. Python code can + * also take some advantage of log level control interface. The developer can + * now leave their logging code permantly active in the source code without + * impact on performance, and the log can be easily turned on/off dynamically + * using Python console for debugging purpose, even in release build. + * + * \section SampleUsage Sample Usage + * + * A set of macros is provided to ease the usage of tag based log. All the + * macros are defined in . At the begining of your C++ source, + * you need to initialize the log level of your chosen tag using, + * + * \code{.c} + * FC_LOG_LEVEL_INIT(tag) + * \endcode + * + * It makes sense to use the same tag in multiple files for easy log level + * control. Please check \ref Customization if You want more than one tag inside + * the same source file. + * + * Predefined log levels are, + * + * \code{.c} + * #define FC_LOGLEVEL_ERR 0 + * #define FC_LOGLEVEL_WARN 1 + * #define FC_LOGLEVEL_MSG 2 + * #define FC_LOGLEVEL_LOG 3 + * #define FC_LOGLEVEL_TRACE 4 + * \endcode + * + * Bigger log level integer values have lower priorities. There is a special log + * level, + * + * \code{.c} + * #define FC_LOGLEVEL_DEFAULT -1 + * \endcode + * + * Actually, any negative log level behave the same, which is for tags + * that are not previously configured by user. The actual log level applied is + * controlled by \c Base::Console().SetDefaultLogLevel(). Python + * developers/end-users can configure the default log level by calling + * + * \code{.py} + * FreeCAD.setLogLevel('Default',level) + * FreeCAD.setLogLevel('DebugDefault',level) + * \endcode + * + * where \c level is either a string of value Error, Warning, Message, Log, + * Trace or an integer value. By default, on release build, the default log + * level is \c FC_LOGLEVEL_MSG, and on debug build, \c FC_LOGLEVEL_LOG. + * + * Python code can call \c FreeCAD.setLogLevel(tag,level) to configure any other + * tag log levels, and \c FreeCAD.getLogLevel(tag), which outputs only integer + * log level. + * + * You can fine tune how the log is output by passing extra parameters to + * #FC_LOG_LEVEL_INIT(). All the extra parameters are boolean value, which are + * shown blew along with their default values. + * + * \code{.c} + * FC_LOG_LEVEL_INIT(tag, print_tag=true, print_src=false, + * print_time=false, add_eol=true, refresh=false) + * \endcode + * + * You can dynamically modify the log style as well, by changing these + * variables before the actual log output macro. See \ref Customization for + * more details + * + * \code{.c} + * FC_LOG_INSTANCE.refresh = true; // print time for each log, default false. + * FC_LOG_INSTANCE.print_src = true; // print file and line number, default false. + * FC_LOG_INSTANCE.print_tag = false; // do not print tag, default true + * FC_LOG_INSTANCE.add_eol = false; // do not add eol + * FC_LOG_INSTANCE.refresh = true; // refresh GUI after each log + * \endcode + * + * Be careful with 'refresh' option. Its current implementation calls + * QCoreApplication::sendPostedEvent() which may cause some unexpected + * behavior, especially when called inside a destructor. + * + * The actual logging macros are + * + * \code + * FC_ERR(msg) + * FC_WARN(msg) + * FC_MSG(msg) + * FC_LOG(msg) + * FC_TRACE(msg) + * \endcode + * + * The logging macros correspond to existing Base::Console() output functions, + * and \c FC_TRACE uses Base::Console().Log(), same as \c FC_LOG. These macros + * checks the log level defined in \c FC_LOG_LEVEL_INIT to decide whether to + * print log or not. \c msg here shall be a C++ streaming expression. End of + * line will be automatically appended by default. + * + * \code + * FC_ERR("error: " << code << ". exiting") + * \endcode + * + * \section TimingHelper Timing Helpers + * + * This set of macros is for helping C++ code to time lengthy operations. + * Examples: + * + * \code{.c} + * void operation() { + * FC_TIME_INIT(t); + * + * //do stuff + * + * FC_TIME_LOG(t,"operation done."); + * } + * \endcode + * + * This will output in console something like, + * + * \code + * operation done. time: 1.12s + * \endcode + * + * Every time you call \c FC_TIME_LOG it will calculate the time duration + * between this call and the last \c FC_TIME_LOG or \c FC_TIME_INIT. Time + * variable \c t will then be updated to the current time. You can also use + * FC_TIME_MSG, FC_TIME_TRACE similar to FC_MSG and FC_TRACE. + * + * To time operation in multiple stages, + * + * \code{.cpp} + * void operation() { + * FC_TIME_INIT2(t,t1); + * + * //do stage 1 + * + * FC_TIME_LOG(t1,"stage1"); + * + * //do stage 2 + * + * FC_TIME_LOG(t1,"stage2"); + * + * // do other stuff + * + * FC_TIME_LOG(t,"total"); + * } + * \endcode + * + * Will output something like, + * \code + * stage1 time: 1.2s + * stage2 time: 2.3s + * total time: 4.0s + * \endcode + * + * To time operation in multiple functions, + * + * \code{.cpp} + * class Timing { + * FC_DURATION_DECLARE(d1) + * FC_DURATION_DECLARE(d1_1) + * FC_DURATION_DECLARE(d1_2) + * FC_DURATION_DECLARE(d2); + * + * Timing() { + * FC_DURATION_INIT(d1); + * FC_DURATION_INIT(d1_1); + * FC_DURATION_INIT(d1_2); + * FC_DURATION_INIT(d2); + * } + * }; + * + * void operation1(Timing &timing) { + * + * FC_TIME_INIT(t); + * + * for(...) { + * FC_TIME_INIT(t1); + * + * //do setp 1 + * + * FC_DURATION_PLUS(timing.d1_1,t1); + * + * // do step 2 + * + * FC_DURATION_PLUS(timing.d1_2,t1); + * } + * + * // do other stuff + * + * FC_DRUATION_PLUS(timing.d1, t); + * } + * + * void operation2(Timing &timing) { + * + * FC_TIME_INIT(t); + * + * // do stuff + * + * FC_DRUATION_PLUS(timing.d2, t); + * } + * + * void operation() { + * + * Timing timing; + * + * FC_TIME_INIT(t); + * + * for(...) { + * operation1(timing); + * + * // do other stuff + * + * operation2(timing); + * } + * + * FC_DURATION_LOG(timing.d1_1,"operation 1 step 1"); + * FC_DURATION_LOG(timing.d1_2,"operation 1 step 2"); + * FC_DURATION_LOG(timing.d1,"operation 1"); + * FC_DURATION_LOG(timing.d2,"operation 2"); + * FC_TIME_LOG(t,"operation total"); + * } + * \endcode + * + * You can also use FC_DURATION_MSG, FC_DURATION_TRACE as usual. + * + * If you use only macros provided here to do timing, the entire timing code + * can be complied out by defining \c FC_LOG_NO_TIMING before including + * \c App/Console.h. + * + * \section Customization + * + * Most of the logging facilities are exposed through macros. This section + * briefs how they are implemented under the hood in case you want + * customization. A new function GetLogLevel(tag) is added to Base::Console() + * to let C++ developer query a log level for an arbitary string tag. The + * function returns a pointer to an integer representing the log level. Python + * developer or end-user can set/get the same tag based log level using + * FreeCAD.setLogLevel/getLogLevel. Any change to the log level is reflected + * through the pointer returned by Base::Console().GetLogLevel(). What + * \c FC_LOG_LEVEL_INIT(tag) does is to define a class Base::LogLevel, and then + * a file static instance of that class to store the pointer to the desired tag + * log level. The class and instance name is predefined. Various log macros + * will check that instance to query log level. If you some how want to have + * more than one tag inside the same source file, use the following macros to + * define a second instance of name \c instance_name + * + * \code + * _FC_LOG_LEVEL_INIT(instance_name,tag) + * \endcode + * + * Then, define a second set of logging macros as + * + * \code{.c} + * #define MY_MSG(_msg) _FC_PRINT(instance_name,FC_LOGLEVEL_MSG,Message,_msg) + * #define MY_WARN(_msg) _FC_PRINT(instance_name,FC_LOGLEVEL_WARN,Warning,_msg) + * #define MY_ERR(_msg) _FC_PRINT(instance_name,FC_LOGLEVEL_ERR,Error,_msg) + * #define MY_LOG(_msg) _FC_PRINT(instance_name,FC_LOGLEVEL_LOG,Log,_msg) + * #define MY_TRACE(_msg) _FC_PRINT(instance_name,FC_LOGLEVEL_TRACE,Log,_msg) + * \endcode + * + * Note, replace \c instance_name with your actual desired name. + * + * You can also define your own log levels the same way. Macro + * #_FC_PRINT(_instance,_l,_func,_msg) checks to see if the log shall proceed, + * where \c _instance is the static loglevel instance name (default is + * \c FC_LOG_INSTANCE), and \c _l is the log level constant to be checked, + * \c _func is the Base::Console() function to print the log. + * + */ + +#define FC_LOGLEVEL_DEFAULT -1 +#define FC_LOGLEVEL_ERR 0 +#define FC_LOGLEVEL_WARN 1 +#define FC_LOGLEVEL_MSG 2 +#define FC_LOGLEVEL_LOG 3 +#define FC_LOGLEVEL_TRACE 4 + +#define _FC_LOG_LEVEL_INIT(_name,_tag,...) \ + static Base::LogLevel _name(_tag,## __VA_ARGS__); + +#ifndef FC_LOG_INSTANCE +# define FC_LOG_INSTANCE _s_fclvl +#endif + +#define FC_LOG_LEVEL_INIT(_tag,...) \ + _FC_LOG_LEVEL_INIT(FC_LOG_INSTANCE, _tag, ## __VA_ARGS__) + +#define _FC_PRINT(_instance,_l,_func,_msg) do{\ + if(_instance.isEnabled(_l)) {\ + std::stringstream str;\ + _instance.prefix(str,__FILE__,__LINE__) << _msg;\ + if(_instance.add_eol) \ + str< + +# define _FC_TIME_INIT(_t) _t=std::chrono::FC_TIME_CLOCK::now() +# define FC_TIME_INIT(_t) FC_TIME_POINT _FC_TIME_INIT(_t) +# define FC_TIME_INIT2(_t1,_t2) FC_TIME_INIT(_t1),_t2=_t1 +# define FC_TIME_INIT3(_t1,_t2,_t3) FC_TIME_INIT(_t1),_t2=_t1,_t3=_t1 + +# define _FC_DURATION_PRINT(_l,_d,_msg) \ + FC_##_l(_msg<< " time: " << _d.count()<<'s'); + +# define FC_DURATION_MSG(_d,_msg) _FC_DURATION_PRINT(MSG,_d,_msg) +# define FC_DURATION_LOG(_d,_msg) _FC_DURATION_PRINT(LOG,_d,_msg) +# define FC_DURATION_TRACE(_d,_msg) _FC_DURATION_PRINT(TRACE,_d,_msg) + +# define _FC_TIME_PRINT(_l,_t,_msg) \ + _FC_DURATION_PRINT(_l,Base::GetDuration(_t),_msg); + +# define FC_TIME_MSG(_t,_msg) _FC_TIME_PRINT(MSG,_t,_msg) +# define FC_TIME_LOG(_t,_msg) _FC_TIME_PRINT(LOG,_t,_msg) +# define FC_TIME_TRACE(_t,_msg) _FC_TIME_PRINT(TRACE,_t,_msg) + +# define FC_DURATION_DECLARE(_d) FC_DURATION _d +# define FC_DURATION_DECLARE2(_d,_d1) FC_DURATION_DECLARE(_d),_d1 +# define FC_DURATION_DECLARE3(_d,_d1) FC_DURATION_DECLARE2(_d,_d1),_d2 + +# define FC_DURATION_INIT(_d) _d=FC_DURATION(0) +# define FC_DURATION_INIT2(_d,_d1) _d=_d1=FC_DURATION(0) +# define FC_DURATION_INIT3(_d,_d1,_d2) _d=_d1=_d2=FC_DURATION(0) + +# define FC_DURATION_DECL_INIT(_d) FC_DURATION _d(0) +# define FC_DURATION_DECL_INIT2(_d,_d1) FC_DURATION_DECL_INIT(_d),_d1(0) +# define FC_DURATION_DECL_INIT3(_d,_d1) FC_DURATION_DECL_INIT2(_d,_d1),_d3(0) + +# define FC_DURATION_PLUS(_d,_t) _d += Base::GetDuration(_t) + +#else //FC_LOG_NO_TIMING +# define FC_TIME_POINT +# define _FC_TIME_INIT(...) do{}while(0) +# define FC_TIME_INIT(...) do{}while(0) +# define FC_TIME_INIT2(...) do{}while(0) +# define FC_TIME_INIT3(...) do{}while(0) +# define _FC_DURATION_PRINT(...) do{}while(0) +# define _FC_TIME(_t) do{}while(0) +# define FC_DURATION_PRINT(...) do{}while(0) +# define FC_DURATION +# define FC_DURATION_INIT(...) do{}while(0) +# define FC_DURATION_INIT1(...) do{}while(0) +# define FC_DURATION_INIT2(...) do{}while(0) +# define FC_DURATION_DECLARE(...) +# define FC_DURATION_DECLARE1(...) +# define FC_DURATION_DECLARE2(...) +# define FC_DURATION_DECL_INIT(...) do{}while(0) +# define FC_DURATION_DECL_INIT2(...) do{}while(0) +# define FC_DURATION_DECL_INIT3(...) do{}while(0) +# define FC_DURATION_PLUS(...) do{}while(0) + +#endif //FC_LOG_NO_TIMING namespace Base { class ConsoleSingleton; @@ -59,6 +438,16 @@ typedef unsigned int ConsoleMsgFlags; namespace Base { +#ifndef FC_LOG_NO_TIMING + inline FC_DURATION GetDuration(FC_TIME_POINT &t) + { + auto tnow = std::chrono::FC_TIME_CLOCK::now(); + auto d = std::chrono::duration_cast(tnow-t); + t = tnow; + return d; + } +#endif + /** The console observer class * This class distribute the Messages issued to the FCConsole class. * If you need to catch some of the Messages you need to inherit from @@ -144,6 +533,16 @@ public: /// Enables or disables message types of a certain console observer bool IsMsgTypeEnabled(const char* sObs, FreeCAD_ConsoleMsgType type) const; + int *GetLogLevel(const char *tag, bool create=true); + + void SetDefaultLogLevel(int level) { + _defaultLogLevel = level; + } + + inline int LogLevel(int level) const{ + return level<0?_defaultLogLevel:level; + } + /// singleton static ConsoleSingleton &Instance(void); @@ -152,6 +551,9 @@ public: static PyMethodDef Methods[]; + void Refresh(); + void EnableRefresh(bool enable); + protected: // python exports goes here +++++++++++++++++++++++++++++++++++++++++++ // static python wrapper of the exported functions @@ -163,6 +565,7 @@ protected: static PyObject *sPyGetStatus(PyObject *self,PyObject *args,PyObject *kwd); bool _bVerbose; + bool _bCanRefresh; // Singleton! ConsoleSingleton(void); @@ -181,6 +584,9 @@ private: // observer list std::set _aclObservers; + + std::map _logLevels; + int _defaultLogLevel; }; /** Access to the Console @@ -191,6 +597,47 @@ inline ConsoleSingleton &Console(void){ return ConsoleSingleton::Instance(); } +class BaseExport ConsoleRefreshDisabler { +public: + ConsoleRefreshDisabler() { + Console().EnableRefresh(false); + } + + ~ConsoleRefreshDisabler() { + Console().EnableRefresh(true); + } +}; + + +/** LogLevel helper class */ +class BaseExport LogLevel { +public: + std::string tag; + int &lvl; + bool print_tag; + bool print_src; + bool print_time; + bool add_eol; + bool refresh; + + LogLevel(const char *tag, bool print_tag=true, bool print_src=false, + bool print_time=false, bool add_eol=true, bool refresh=false) + :tag(tag),lvl(*Console().GetLogLevel(tag)) + ,print_tag(print_tag),print_src(print_src),print_time(print_time) + ,add_eol(add_eol),refresh(refresh) + {} + + bool isEnabled(int l) { + return l<=level(); + } + + int level() const { + return Console().LogLevel(lvl); + } + + std::stringstream &prefix(std::stringstream &str, const char *src, int line); +}; + //========================================================================= // some special observers