Newer
Older
/*!
* \author Klaus Zenker (HZDR)
* \date 03.04.2018
* \page loggingdoc Logging module and Logger
* \section logginintro Introduction to the logging mechanism
* The logging provided here requires to add the LoggingModule to your
Application.
* The module introduces the following input variables, that need to be
connected to the control system:
* - targetStream: Allows to choose where messages send to the logging module
end up:
* - 0: cout/cerr+logfile
* - 1: logfile
* - 2: cout/cerr
* - 3: nowhere
* - logFile: Give the logfile name. If the file is not empty logging messages
will be appended. If
* you choose targetStream 0 or 1 and don't set a logFile the Logging module
simply skips the file
* writing.
* - logLevel: Choose a certain logging level of the Module. Messages send to
the Logging module also include a logging
* level. The Logging module compares both levels and decides if a message is
dropped (e.g. message level is
* DEBUG and Module level is ERROR) or broadcasted.
* - tailLength: The number of messages published by the Logging module (see
logTail), i.e. to the control system.
* This length has no influence on the targetStreams, that receive all
messages (depending on the logLevel). The
* logLevel also applies to messages that are published by the Logging module
via the logTail
*
* Available logging levels are:
* - DEBUG
* - INFO
* - WARNING
* - ERROR
* - SILENT
*
* The only variable that is published by the Logging module is the logTail. It
contains the list of latest messages.
* Messages are separated by a newline character. The number of messages
published in the logTail is set via the
* input variable tailLength. Other than that, messages are written to
cout/cerr and/or a log file as explained above.
*
* In order to add a source to the Logging module the method \c
addSource(Logger*) is
* available.
* The foreseen way of using the Logger is to add a Logger to a module that
should send log messages.
* In the definition of connections of the application (\c defineConnections()
) one can add this source to the Logging module.
*
* The following example shows how to integrate the Logging module and the
Logging into an application (myApp) and one module sending
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
* messages (TestModule).
* \code
* sruct TestModule: public ChimeraTK::ApplicationModule{
* Logger{this};
* ...
* };
* struct myApp : public ChimeraTK::Application{
*
* LoggingModule log { this, "LoggingModule", "LoggingModule test" };
*
* ChimeraTK::ControlSystemModule cs;
*
* TestModule { this, "test", "" };
* ...
* };
*
*
* void myAPP::defineConnctions(){
* cs["Logging"]("targetStream") >> log.targetStream;
* cs["Logging"]("logLevel") >> log.logLevel;
* cs["Logging"]("logFile") >> log.logFile;
* cs["Logging"]("tailLength") >> log.tailLength;
* log.addSource(&TestModule.logger)
* ...
* }
*
* void TestModule::mainLoop{
* logger.sendMessage("Test",LogLevel::DEBUG);
* ...
* }
* \endcode
*
* A message always looks like this:
* LogLevel::LoggingModuleName/SendingModuleName TimeString -> message\n
* In the example given above the meassge could look like:
* \code
* DEBUG::LogggingModule/test 2018-Apr-12 14:03:07.947949 -> Test
* \endcode
* \remark Instead of adding a Logger to every module that should feed the
Logging module, one could also consider using only one Logger object.
* This is not thread safe and would not work for multiple modules trying to
send messages via the Logger object to the Logging module at the same time.
*/
#ifndef MODULES_LOGGING_H_
#define MODULES_LOGGING_H_
#undef GENERATE_XML
namespace ctk = ChimeraTK;
/** Pair of message and messageLevel */
using Message =
std::pair<ctk::ScalarPushInput<std::string>, ctk::ScalarPushInput<uint>>;
/** Define available logging levels. */
enum LogLevel { DEBUG, INFO, WARNING, ERROR, SILENT };
/** Define stream operator to use the LogLevel in streams, e.g. std::cout */
std::ostream &operator<<(std::ostream &os, const LogLevel &level);
/** Construct a sting containing the current time. */
std::string getTime();
/**
* \brief Class used to send messages in a convenient way to the LoggingModule.
*
* In principle this class only adds two output variables and provides a simple
* method to fill these variables. They are supposed to be connected to the
* LoggingModule via LoggingModule::addSource. If sendMessage is used before
* chimeraTK process variables are initialized an internal buffer is used to
* store those messages. Once the process variables are initialized the messages
* from the buffer are send. \attention This only happens once a message is send
* after chimeraTK process variables are initialized! In other words if no
* message is send in the mainLoop messages from defineConnections will never be
* shown.
*/
std::queue<std::pair<std::string, logging::LogLevel>> msg_buffer;
public:
/**
* \brief Default constructor: Allows late initialization of modules (e.g.
* when creating arrays of modules).
*
* This constructor also has to be here to mitigate a bug in gcc. It is
* needed to allow constructor inheritance of modules owning other modules.
* This constructor will not actually be called then. See this bug report:
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */
Logger(){};
/**
* \brief Constructor to be used.
*
* \param module The owning module that is using the Logger. It will appear as
* sender in the LoggingModule, which is receiving messages from the Logger.
*/
Logger(ctk::Module *module);
/** Message to be send to the logging module */
ctk::ScalarOutput<std::string> message;
/** Message to be send to the logging module */
ctk::ScalarOutput<uint> messageLevel;
/**
* \brief Send a message, which means to update the message and messageLevel
* member variables.
*
*/
void sendMessage(const std::string &msg, const logging::LogLevel &level);
};
/**
* \brief Module used to handle logging messages.
*
* A ChimeraTK module is producing messages, that are send to the LoggingModule
* via the \c message variable. The message is then put into the logfile ring
* buffer and published in the \c LogFileTail. In addidtion the message can be
* put to an ostream. Available streams are:
* - file stream
* - cout/cerr
*
* You can control which stream is used by setting the targetStream varibale:
* 0: cout/cerr and logfile
* 1: logfile
* 2: cout/cerr
* 3: none
*
* The logfile is given by the client using the logFile variable.
*/
class LoggingModule : public ctk::ApplicationModule {
private:
std::pair<ctk::VariableNetworkNode, ctk::VariableNetworkNode>
getAccessorPair(const std::string &sender);
/** Map key is the feeding module */
std::map<std::string, Message> msg_list;
/** Update message or messageLevel from the sending module
* Basically the first element read by readAny() could be either the message
* or the messageLevel. Here the element not updated by readSAny is also read,
* since both are PushInput variables.
*/
std::map<std::string, Message>::iterator
UpdatePair(const ChimeraTK::TransferElementID &id);
/** Number of messages stored in the tail */
size_t messageCounter;
/** Broadcast message to cout/cerr and log file
* \param msg The mesage
* \param isError If true cerr is used. Else cout is used.
*/
void broadcastMessage(std::string msg, bool isError = false);
public:
using ctk::ApplicationModule::ApplicationModule;
ctk::ScalarPollInput<uint> targetStream{
this, "targetStream", "",
"Set the tagret stream: 0 (cout/cerr+logfile), 1 (logfile), 2 "
"(cout/cerr), 3 (none)"};
ctk::ScalarPollInput<std::string> logFile{
this, "Logfile", "",
"Name of the external logfile. If empty messages are pushed to "
"cout/cerr"};
ctk::ScalarPollInput<uint> tailLength{
this, "maxLength", "",
"Maximum number of messages to be shown in the logging stream tail."};
ctk::ScalarPollInput<uint> logLevel{this, "logLevel", "",
"Current log level used for messages."};
ctk::ScalarOutput<std::string> logTail{this,
"LogTail",
"",
"Tail of the logging stream.",
{"CS", "PROCESS", getName()}};
std::unique_ptr<std::ofstream> file; ///< Log file where to write log messages
/** Add a Module as a source to this DAQ. */
void addSource(Logger *logger);
/**
* Application core main loop.
*/
void mainLoop() override;
void terminate() override;
};
#endif /* MODULES_LOGGING_H_ */