Logging

In ldmx-sw, we use the logging library from boost. The initialization (and de-initialization) procedures are housed in the Logger header and implementation files in the Framework module. The general idea is for each class to have its own logger which gives a message and some meta-data to the boost logging core which then filters it before dumping the message to sinks. In our case, we have two (optional) sinks: the terminal and a logging file. All of the logging configuration is done by the parameters passed to the Process in the python configuration file.

Configuration

The python class Process has three parameters that configure how the logging works.

ParameterDescription
logFileNameName of logging file. No logging to file is done if this is not set.
termLogLevelLogging level (and above) to print to terminal
fileLogLevelLogging level (and above) to print to file

The logging levels are defined in the Logger header file, and a general description is given below. Right now, configuration uses the int equivalent while the C++ uses the enum levels.

Basics: Logging in Processors

In order to save time and lines of code, we have defined a macro that can be used anywhere in ldmx-sw.

ldmx_log(info) << My message goes here;

info is the severity level of this message. A severity level can be any of the following:

LevelIntDescriptionExample
debug0Extra-detailed information that you would want if you were suspicious that things were running correctlyWhether a sensitive detector is skipping a hit or not
info1Helpful comments for checking operationsWhat processes are enabled in Geant4
warn2Something wrong happened but its not too badREGEX mismatch in GDML parsing
error3Something really wrong happened and the user needs to know that something needs to changeExtra information before an EXCEPTION_RAISE calls
fatal4Reserved for run-ending exceptionsMessage from catches of Exception.

This is really all you need for an EventProcessor. Notice that the logger does not need a new line character at the end of the line. The logger automatically makes a new line after each message.

There is an additional logging level below "debug". A lot of times during development, a developer puts messages inside of the code to make sure it is working properly. A lot of these extra messages are not very helpful for the end user, so they should go into the log at all. Feel free to leave these messages in, but comment them out so that they don't clog up the log. A good rule of thumb is that the debug channel should have at most one message per event, info - one message every few events, warn - a few messages total per run, and error only one message per run.

More Detail: Logging Outside Processors

There are some other inheritance trees inside of ldmx-sw that have theLog_ as a protected member variable (like how it is set up for processors). For example, XsecBiasingOperator, UserAction, and PrimaryGenerator, so you may not need to define a new member theLog_ in your class if it inherits from a class with theLog_ already defined and accessible (i.e. public or protected).

There is a lot going on under the hood for this logging library, but I can give slightly more detail to help you if you want to have logging inside of a class that does not inherit from a class that already has the log defined. Basically, each class has a member variable called theLog_ that is constructed by makeLogger. The logger has an associated attribute that boost calls a "channel": a short name for the source of the message. For example, inside of a processor, the channel is the name of the processor. There is another convenience wrapper around the necessary code to "enable logging" inside of your class. Again, this is best illustrated by an example:

//myClass.h
#include "Exception/Logger.h" //<-- defines the macros you need
class myClass {
 public:
  void someFunctionThatLogs() {
    ldmx_log(info) << "I can log!"; //<-- macro that calls theLog_
  }
 private:
  /** enable logging for myClass */
  enableLogging("myClass") //<-- macro that declares and defines theLog_
};

Notice that the macro enableLogging is in the class declaration. It needs to be here because it declares and defines the member variable theLog_ --- it is good practice to keep theLog_ private, so put the macro in the private branch of your class declaration. You can provide any channel name to enableLogging that you wish, but the class name is a good name to default to.