/*************************************************************************** * (c) Jürgen Riegel (juergen.riegel@web.de) 2002 * * * * This file is part of the FreeCAD CAx development system. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License (LGPL) * * as published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * for detail see the LICENCE text file. * * * * FreeCAD is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with FreeCAD; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * * USA * * * * Juergen Riegel 2002 * ***************************************************************************/ #ifndef BASE_CONSOLE_H #define BASE_CONSOLE_H // Std. configurations #include #include //#pragma warning(disable: 4786) // specifier longer then 255 chars #include #include #include #include #include #include #include //************************************************************************** // Loging levels #ifdef FC_DEBUG /// switch on the loging of python object creation and destruction # undef FC_LOGPYOBJECTS /// switch on the loging of Feature update and execution # define FC_LOGFEATUREUPDATE /// switch on the loging of the Update execution through Doc, App, GuiApp and GuiDoc # 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; } // namespace Base typedef Base::ConsoleSingleton ConsoleMsgType; 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 * this class and implement some of the methods. * @see Console */ class BaseExport ConsoleObserver { public: ConsoleObserver() :bErr(true),bMsg(true),bLog(true),bWrn(true) {} virtual ~ConsoleObserver() {} /// get calles when a Warning is issued virtual void Warning(const char *){} /// get calles when a Message is issued virtual void Message(const char *){} /// get calles when a Error is issued virtual void Error (const char *)=0; /// get calles when a Log Message is issued virtual void Log (const char *){} virtual const char *Name(void){return 0L;} bool bErr,bMsg,bLog,bWrn; }; /** The console class * This class manage all the stdio stuff. This includes * Messages, Warnings, Log entries and Errors. The incoming * Messages are distributed with the FCConsoleObserver. The * FCConsole class itself makes no IO, it's more like a manager. * \par * ConsoleSingleton is a singleton! That means you can access the only * instance of the class from every where in c++ by simply using: * \code * #include * Base::Console().Log("Stage: %d",i); * \endcode * \par * ConsoleSingleton is able to switch between several modes to, e.g. switch * the logging on or off, or treat Warnings as Errors, and so on... * @see ConsoleObserver */ class BaseExport ConsoleSingleton { public: // exported functions goes here +++++++++++++++++++++++++++++++++++++++ /// Prints a Message virtual void Message ( const char * pMsg, ... ) ; /// Prints a warning Message virtual void Warning ( const char * pMsg, ... ) ; /// Prints a error Message virtual void Error ( const char * pMsg, ... ) ; /// Prints a log Message virtual void Log ( const char * pMsg, ... ) ; /// Delivers a time/date string const char* Time(void); /// Attaches an Observer to FCConsole void AttachObserver(ConsoleObserver *pcObserver); /// Detaches an Observer from FCConsole void DetachObserver(ConsoleObserver *pcObserver); /// enumaration for the console modes enum ConsoleMode{ Verbose = 1, // suppress Log messages }; enum FreeCAD_ConsoleMsgType { MsgType_Txt = 1, MsgType_Log = 2, // ConsoleObserverStd sends this and higher to stderr MsgType_Wrn = 4, MsgType_Err = 8 } ; /// Change mode void SetMode(ConsoleMode m); /// Change mode void UnsetMode(ConsoleMode m); /// Enables or disables message types of a certain console observer ConsoleMsgFlags SetEnabledMsgType(const char* sObs, ConsoleMsgFlags type, bool b); /// 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); // retrieval of an observer by name ConsoleObserver *Get(const char *Name) const; static PyMethodDef Methods[]; void Refresh(); void EnableRefresh(bool enable); protected: // python exports goes here +++++++++++++++++++++++++++++++++++++++++++ // static python wrapper of the exported functions static PyObject *sPyLog (PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sPyMessage (PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sPyWarning (PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sPyError (PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sPySetStatus(PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sPyGetStatus(PyObject *self,PyObject *args,PyObject *kwd); bool _bVerbose; bool _bCanRefresh; // Singleton! ConsoleSingleton(void); virtual ~ConsoleSingleton(); private: // singleton static void Destruct(void); static ConsoleSingleton *_pcSingleton; // observer processing void NotifyMessage(const char *sMsg); void NotifyWarning(const char *sMsg); void NotifyError (const char *sMsg); void NotifyLog (const char *sMsg); // observer list std::set _aclObservers; std::map _logLevels; int _defaultLogLevel; }; /** Access to the Console * This method is used to gain access to the one and only instance of * the ConsoleSingleton class. */ 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 /** The LoggingConsoleObserver class * This class is used by the main modules to write Console messages and logs to a file */ class BaseExport ConsoleObserverFile : public ConsoleObserver { public: ConsoleObserverFile(const char *sFileName); virtual ~ConsoleObserverFile(); virtual void Warning(const char *sWarn); virtual void Message(const char *sMsg); virtual void Error (const char *sErr); virtual void Log (const char *sLog); const char* Name(void){return "File";} protected: Base::ofstream cFileStream; }; /** The CmdConsoleObserver class * This class is used by the main modules to write Console messages and logs the system con. */ class BaseExport ConsoleObserverStd: public ConsoleObserver { public: ConsoleObserverStd(); virtual ~ConsoleObserverStd(); virtual void Warning(const char *sWarn); virtual void Message(const char *sMsg); virtual void Error (const char *sErr); virtual void Log (const char *sErr); const char* Name(void){return "Console";} protected: bool useColorStderr; }; class BaseExport RedirectStdOutput : public std::streambuf { public: RedirectStdOutput(); protected: int overflow(int c = EOF); int sync(); private: std::string buffer; }; class BaseExport RedirectStdError : public std::streambuf { public: RedirectStdError(); protected: int overflow(int c = EOF); int sync(); private: std::string buffer; }; class BaseExport RedirectStdLog : public std::streambuf { public: RedirectStdLog(); protected: int overflow(int c = EOF); int sync(); private: std::string buffer; }; } // namespace Base #endif // BASE_CONSOLE_H