Warning, /EICrecon/docs/design/logging.md is written in an unsupported language. File is not indexed.
0001 # Logging
0002
0003 EICrecon uses [spdlog](https://github.com/gabime/spdlog) library as a logging backbone. **spdlog** utilizes
0004 [fmt](https://github.com/fmtlib/fmt) - a string formatting and output library which uses python alike string formatting
0005 ([is C++20 standard](https://en.cppreference.com/w/cpp/utility/format)) and is very performant
0006 (in many cases [is faster than printf and std streams](https://github.com/fmtlib/fmt#speed-tests))
0007
0008
0009 ## Log levels
0010
0011 **What log levels mean**
0012
0013 - **trace** - something very verbose like each hit parameter
0014 - **debug** - all information that is relevant for an expert to debug but should not be present in production
0015 - **info** - something that will always (almost) get into the global production log
0016 - **warning** - something that needs attention but the overall results are likely usable
0017 - **error** - something bad making results are probably UNUSABLE
0018 - **critical** - imminent software failure, crash and termination
0019
0020 Global log can be controlled through:
0021
0022 ```bash
0023 -Peicrecon:LogLevel=info # trace, debug, info, warning, error, critical, off
0024 ```
0025
0026 Each plugin or even factory can create its own logger, which clones default logger parameters
0027 but then could be set separately. But convention `<prefix>:LogLevel=<level>`. E.g.:
0028
0029 ```bash
0030 -Pacts:LogLevel=debug
0031 -PTracking:CentralTrackingParticles:LogLevel=trace
0032 ```
0033
0034
0035 ## Basic use
0036
0037 EICRecon has a log service that centralizes default logger configuration and helps spawn named loggers.
0038 Each unit - a plugin, factory, class, etc. can spawn its own named logger and use it:
0039
0040 [FULL WORKING EXAMPLE](https://github.com/eic/EICrecon/blob/main/src/examples/log_example/log_example.cc)
0041
0042 ```cpp
0043 #include <services/log/Log_service.h>
0044 class ExampleProcessor: public JEventProcessor {
0045 private:
0046 std::shared_ptr<spdlog::logger> m_log;
0047
0048 public:
0049 /// Once in m_app lifetime function call
0050 void Init() override {
0051
0052 auto m_app = GetApplication();
0053
0054 // The service centralizes the use of spdlog and properly spawning logger
0055 auto log_service = m_app->GetService<Log_service>();
0056
0057 // Loggers are spawned by name.
0058 m_log = log_service->logger("ExampleProcessor");
0059
0060 // log things!
0061 m_log->info("Hello world! {}", 42);
0062 }
0063
0064 /// The function is executed every event
0065 void Process(const std::shared_ptr<const JEvent>& event) override {
0066 // Will print something if 'trace' log level is set (see below)
0067 m_log->trace("Processing event #{}", event->GetEventNumber());
0068 // ...
0069 }
0070 ```
0071
0072
0073
0074 ## Formatting
0075
0076 Thanks to fmt, logger has rich text formatting. More examples and full
0077 specification is in [fmt documentation](https://github.com/fmtlib/fmt):
0078
0079 ```cpp
0080 m_log->info("Welcome to spdlog!");
0081 // [info] Welcome to spdlog!
0082
0083 m_log->error("Some error message with arg: {}", 1);
0084 // [error] Some error message with arg: 1
0085
0086 m_log->warn("Easy padding in numbers like {:08d}", 12);
0087 // [warning] Easy padding in numbers like 00000012
0088
0089 m_log->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
0090 // [critical] Support for int: 42; hex: 2a; oct: 52; bin: 101010
0091
0092 m_log->info("Support for floats number of digits {:03.2f}", 1.23456);
0093 // [info] Support for floats number of digits 1.23
0094
0095 m_log->info("Positional args are {1} {0}...", "too", "supported");
0096 // [info] Positional args are supported too...
0097
0098 m_log->info("{:>30}", "right aligned");
0099 // [info] right aligned
0100
0101 m_log->info("Table of values:");
0102 m_log->info("{:<5} {:<7} {:>10}", "N", "val1", "val2");
0103 m_log->info("{:=^30}", "centered"); // use '=' as a fill char
0104 m_log->info("{:<5} {:<7.2f} {:>10}", 23, 3.1415, 3.1415);
0105 m_log->info("{:<5} {:<7.2f} {:>10}", 24, 2.7182, 2.7182);
0106 m_log->info("{:<5} {:<7.2f} {:>10}", 25, 1.6180, 1.6180);
0107 // [info] Table of values:
0108 // [info] N val1 val2
0109 // [info] ===========centered===========
0110 // [info] 23 3.14 3.1415
0111 // [info] 24 2.72 2.7182
0112 // [info] 25 1.62 1.618
0113
0114 // Compile time log levels
0115 // define SPDLOG_ACTIVE_LEVEL to desired level e.g.:
0116 // #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
0117 SPDLOG_TRACE("Some trace message with param {}", 42);
0118 SPDLOG_DEBUG("Some debug message");
0119 ```
0120
0121
0122 ## User parameters
0123
0124 In order to wire your logger level with a jana-parameter to change log level without recompilation, use:
0125
0126 ```cpp
0127 // includes:
0128 #include <services/log/Log_service.h>
0129 #include <extensions/spdlog/SpdlogExtensions.h>
0130
0131 // ... all from previous example
0132 void Init() override {
0133 // ...
0134
0135 // This is a default level
0136 std::string log_level_str = "info";
0137
0138 // Ask service locator for parameter manager. We want to get this plugin parameters.
0139 auto pm = m_app->GetJParameterManager();
0140
0141 // Define parameter
0142 pm->SetDefaultParameter("log_example:log-level", log_level_str, "log_level: trace, debug, info, warn, err, critical, off");
0143
0144 // At this point log_level_str is initialized with user provided flag value or the default value
0145
0146 // convert input std::string to spdlog::level::level_enum and set logger level
0147 m_log->set_level(eicrecon::ParseLogLevel(log_level_str));
0148 }
0149 ```
0150
0151 ## If log level <=
0152
0153
0154
0155 Sometimes one needs to know current log level to calculate debugging values. Use **<=** operator to check level.
0156 It works because enum values are: trace=0, debug=1, ... critical= 5. So:
0157
0158 ```cpp
0159 // In general one doesn't need to manually check log debug level
0160 m_log->debug("Will be printed if level is 'debug' or 'trace' ");
0161
0162 // But sometimes one needs to know current log level to calculate debugging values
0163 // Use <= operator to check level.
0164 // m_log->level() <= spdlog::level::debug means 'debug' or 'trace'
0165 // m_log->level() <= spdlog::level::info means 'info', 'debug' or 'trace' levels
0166 if(m_log->level() <= spdlog::level::debug) {
0167 int x = event->GetRunNumber()*1000000 + event->GetEventNumber()/2;
0168 m_log->debug("Calculated debug value #{}", x);
0169 }
0170 ```
0171
0172
0173
0174 ## Hints
0175
0176
0177
0178 ### Streamable objects
0179
0180 Sometimes you need to print objects with custom overloaded ostr stream operator `<<`
0181 i.e. objects that knows how to print self when used with something like ```cout<<object```
0182 Such objects are used sometimes, e.g. lib eigen matrices might be printed that way.
0183 To enable printing of such objects include the next header
0184
0185 ```cpp
0186 // Include this to print/format streamable objects.
0187 // You don't need this header most of the time (until you see a compilation error)
0188 #include <spdlog/fmt/ostr.h>
0189 ```
0190
0191
0192 ### Default logger
0193
0194 spdlog has a default logger and global functions like `spdlog::info`, `spdlog::warn` etc.
0195
0196 ```cpp
0197 spdlog::info("Hello world from default logger! You can do this, but please don't");
0198 ```
0199
0200 It is possible to use a default logger to print something out, but it is NOT RECOMMENDED to be used instead
0201 of a named loggers. Default logger is used to highlight something that relates to whole application execution.
0202 Otherwise, use named logger.
0203
0204
0205 ### Shared names
0206
0207 By default, spdlog fails if a logger with such name exists (but one can get existing logger
0208 from registry). EICrecon Logger service simplifies and automates it with a single function `logger(name)`.
0209 This allows to use the same logger with the same name from different units if the context is the same.
0210 Imagine you want to highlight that this message belongs to "tracking" you can do:
0211
0212 ```cpp
0213
0214 // One class
0215 m_tracking_log = m_app->GetService<Log_service>()->logger("tracking");
0216
0217 // Another class
0218 m_tracking_log = m_app->GetService<Log_service>()->logger("tracking");
0219 ```
0220
0221 You can mix named loggers depending on context
0222
0223 ```cpp
0224
0225 // Some class INIT
0226 m_this_log = m_app->GetService<Log_service>()->logger("ExampleFactoryName");
0227 m_tracking_log = m_app->GetService<Log_service>()->logger("tracking");
0228
0229 // Some class event PROCESSING
0230 m_this_log->trace("Something related to this class/factory/plugin");
0231 m_tracking_log->info("Something relating to tracking in general");
0232 ```
0233
0234
0235 ### String format
0236
0237 It is recommended to use fmt string formatting both from performance and safety points. But it is also very convenient!
0238 fmt can be accessed through spdlog headers:
0239
0240 ```cpp
0241 #include <spdlog/fmt/fmt.h>
0242
0243 // code
0244 std::string hello = fmt::format("Hello world {}", 42);
0245 ```
0246
0247
0248 ### CMake
0249
0250 spdlog is included by default in every plugin if `plugin_add` macro is used:
0251
0252 ```cmake
0253 plugin_add(${PLUGIN_NAME}) # <= spdlog will be included by default
0254 ```
0255
0256 eicrecon application also includes Log_service by default. So it should not appear on `-Pplugins` list.
0257
0258
0259
0260 ## Additional links
0261
0262 - [spdlog](https://github.com/gabime/spdlog)
0263 - [fmt](https://github.com/fmtlib/fmt)
0264 - [EICrecon logging examples](https://github.com/eic/EICrecon/blob/main/src/examples/log_example/log_example.cc)
0265 - [EICrecon factory with log example](https://github.com/eic/EICrecon/blob/main/src/algorithms/tracking/TrackerHitReconstruction_factory.cc)