Skip to content
Snippets Groups Projects
Logging.h 8.76 KiB
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
 * - 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
 *  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
 *  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
#include "ApplicationCore.h"
#include <fstream>
#include <map>
#include <queue>
namespace logging {
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.
class Logger {
  std::queue<std::pair<std::string, logging::LogLevel>> msg_buffer;

   * \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 */
   * \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 {
  std::pair<ctk::VariableNetworkNode, ctk::VariableNetworkNode>
  getAccessorPair(const std::string &sender);
  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;
};

} // namespace logging