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 the logger member that configures how the logging works.

ParameterDescription
filePathpath to logging file. No logging to file is done if this is not set.
fileLevelLogging level (and above) to print to the file
termLevelLogging level (and above) to print to the terminal

Besides these parameters you can set directly, the logger also has the ability to customize logging levels depending on the name of the logging channel (usually the processor's name).

Examples

to lower the level for everyone

p.logger.termLevel = 0

to debug a specific processor

p.logger.debug(my_processor) # OR p.logger.debug("MyProcessorName")

to silence a specific processor

p.logger.silence(my_processor) # OR p.logger.silence("MyProcessorName")

or something in between

p.logger.custom(my_processor, level = 1)

It is usually better to provide the processor object instead of the string in order to avoid typos, but the string option is there in case you don't have easy access to the channel configuration object yourself.

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:

LevelIntDescriptionExamplePrintout frequency
trace-1Printouts for cases that are more verbose than what you would put for `debugSimHit level information, or processes that are skipped in the end, or printouts that only help to see that the code got to line xyz, but are not very helpful for the end userSeveral dozens
debug0Extra-detailed information that you would want if you were suspicious that things were running correctlyWhether a sensitive detector is skipping a hit or notFew O(10) / event
info1Helpful comments for checking operations, average one message per event per channelWhat processes are enabled in Geant4, event-level information like the number of hitsO(1) / event
warn2Something wrong happened but its not too badREGEX mismatch in GDML parsingO(10) / run
error3Something really wrong happened and the user needs to know that something needs to changeExtra information before an EXCEPTION_RAISE callsO(1) / run
fatal4Reserved for run-ending exceptionsMessage from catches of Exception1 / run

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.

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.