9 #include <boost/python.hpp>
11 #include <framework/pybasf2/LogPythonInterface.h>
13 #include <framework/logging/LogConnectionFilter.h>
14 #include <framework/logging/LogConnectionTxtFile.h>
15 #include <framework/logging/LogConnectionJSON.h>
16 #include <framework/logging/LogConnectionUDP.h>
17 #include <framework/logging/LogConnectionConsole.h>
18 #include <framework/logging/LogVariableStream.h>
19 #include <framework/logging/LogSystem.h>
21 #include <framework/core/Environment.h>
30 using namespace boost::python;
35 if (overrideLevel != LogConfig::c_Default)
36 level = overrideLevel;
38 LogSystem::Instance().getLogConfig()->setLogLevel(level);
43 LogSystem::Instance().getLogConfig()->setAbortLevel(level);
46 void LogPythonInterface::setDebugLevel(
int level)
48 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
53 LogSystem::Instance().getLogConfig()->setLogInfo(level, info);
56 void LogPythonInterface::setPackageLogConfig(
const std::string& package,
const LogConfig& config)
58 LogSystem::Instance().addPackageLogConfig(package, config);
61 void LogPythonInterface::setMaxMessageRepetitions(
unsigned repetitions)
63 LogSystem::Instance().setMaxMessageRepetitions(repetitions);
68 return LogSystem::Instance().getLogConfig()->getLogLevel();
73 return LogSystem::Instance().getLogConfig()->getAbortLevel();
76 int LogPythonInterface::getDebugLevel()
78 return LogSystem::Instance().getLogConfig()->getDebugLevel();
83 return LogSystem::Instance().getLogConfig()->getLogInfo(level);
86 LogConfig& LogPythonInterface::getPackageLogConfig(
const std::string& package)
88 return LogSystem::Instance().getPackageLogConfig(package);
91 unsigned LogPythonInterface::getMaxMessageRepetitions()
const
93 return LogSystem::Instance().getMaxMessageRepetitions();
96 void LogPythonInterface::addLogJSON(
bool complete)
101 void LogPythonInterface::addLogUDP(
const std::string& hostname,
unsigned short port)
103 LogSystem::Instance().addLogConnection(
new LogConnectionUDP(hostname, port));
106 void LogPythonInterface::addLogFile(
const std::string& filename,
bool append)
111 void LogPythonInterface::addLogConsole()
116 void LogPythonInterface::addLogConsole(
bool color)
121 void LogPythonInterface::reset()
123 LogSystem::Instance().resetLogConnections();
126 void LogPythonInterface::zeroCounters()
128 LogSystem::Instance().resetMessageCounter();
131 void LogPythonInterface::enableErrorSummary(
bool on)
133 LogSystem::Instance().enableErrorSummary(on);
136 void LogPythonInterface::setPythonLoggingEnabled(
bool enabled)
const
138 LogConnectionConsole::setPythonLoggingEnabled(enabled);
141 bool LogPythonInterface::getPythonLoggingEnabled()
const
143 return LogConnectionConsole::getPythonLoggingEnabled();
146 void LogPythonInterface::setEscapeNewlinesEnabled(
bool enabled)
const
148 LogConnectionConsole::setEscapeNewlinesEnabled(enabled);
151 bool LogPythonInterface::getEscapeNewlinesEnabled()
const
153 return LogConnectionConsole::getEscapeNewlinesEnabled();
157 dict LogPythonInterface::getLogStatistics()
160 const LogSystem& logSys = LogSystem::Instance();
161 for (
int iLevel = 0; iLevel < LogConfig::c_Default; ++iLevel) {
169 #if !defined(__GNUG__) || defined(__ICC)
171 #pragma GCC diagnostic push
172 #pragma GCC diagnostic ignored "-Wunused-local-typedefs"
175 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(addLogConsole_overloads, addLogConsole, 0, 1)
176 #if !defined(__GNUG__) || defined(__ICC)
178 #pragma GCC diagnostic pop
181 bool terminalSupportsColors()
183 return LogConnectionConsole::terminalSupportsColors(STDOUT_FILENO);
188 void LogPythonInterface::exposePythonAPI()
191 namespace bp = boost::python;
193 docstring_options options(
true,
true,
false);
196 enum_<LogConfig::ELogLevel>(
"LogLevel", R
"DOCSTRING(Class for all possible log levels
200 The lowest possible severity meant for expert only information and disabled
201 by default. In contrast to all other log levels DEBUG messages have an
202 additional numeric indication of their priority called the ``debug_level`` to
203 allow for different levels of verbosity.
205 The agreed values for ``debug_level`` are
207 * **0-9** for user code. These numbers are reserved for user analysis code and
208 may not be used by any part of basf2.
209 * **10-19** for analysis package code. The use case is that a user wants to debug
210 problems in analysis jobs with the help of experts.
212 * **20-29** for simulation/reconstruction code.
213 * **30-39** for core framework code.
215 .. note:: The default maximum debug level which will be shown when
216 running ``basf2 --debug`` without any argument for ``--debug`` is **10**
221 Used for informational messages which are of use for the average user but not
222 very important. Should be used very sparsely, everything which is of no
223 interest to the average user should be a debug message.
225 .. attribute:: RESULT
227 Informational message which don't indicate an error condition but are more
228 important than a mere information. For example the calculated cross section
229 or the output file name.
231 .. deprecated:: release-01-00-00
232 use `INFO <basf2.LogLevel.INFO>` messages instead
234 .. attribute:: WARNING
236 For messages which indicate something which is not correct but not fatal to
237 the processing. This should **not** be used to make informational messages
238 more prominent and they should not be ignored by the user but they are not
243 For messages which indicate a clear error condition which needs to be
244 recovered. If error messages are produced before event processing is started
245 the processing will be aborted. During processing errors don't lead to a stop
246 of the processing but still indicate a problem.
250 For errors so severe that no recovery is possible. Emitting a fatal error
251 will always stop the processing and the `B2FATAL` function is guaranteed to
254 .value(LogConfig::logLevelToString(LogConfig::c_Debug), LogConfig::c_Debug)
255 .value(LogConfig::logLevelToString(LogConfig::c_Info), LogConfig::c_Info)
256 .value(LogConfig::logLevelToString(LogConfig::c_Result), LogConfig::c_Result)
257 .value(LogConfig::logLevelToString(LogConfig::c_Warning), LogConfig::c_Warning)
258 .value(LogConfig::logLevelToString(LogConfig::c_Error), LogConfig::c_Error)
259 .value(LogConfig::logLevelToString(LogConfig::c_Fatal), LogConfig::c_Fatal)
260 .value(LogConfig::logLevelToString(LogConfig::c_Default), LogConfig::c_Default)
264 enum_<LogConfig::ELogInfo>(
"LogInfo", R
"DOCSTRING(The different fields of a log message.
266 These fields can be used as a bitmask to configure the appearance of log messages.
270 The severity of the log message, one of `basf2.LogLevel`
272 .. attribute:: MESSAGE
274 The actual log message
276 .. attribute:: MODULE
278 The name of the module active when the message was emitted. Can be empty if
279 no module was active (before/after processing or outside of the normal event
282 .. attribute:: PACKAGE
284 The package the code that emitted the message belongs to. This is empty for
285 messages emitted by python scripts
287 .. attribute:: FUNCTION
289 The function name that emitted the message
293 The filename containing the code emitting the message
297 The line number in the file emitting the message
299 .value("LEVEL", LogConfig::c_Level)
300 .value(
"MESSAGE", LogConfig::c_Message)
301 .value(
"MODULE", LogConfig::c_Module)
302 .value(
"PACKAGE", LogConfig::c_Package)
303 .value(
"FUNCTION", LogConfig::c_Function)
304 .value(
"FILE", LogConfig::c_File)
305 .value(
"LINE", LogConfig::c_Line)
306 .value(
"TIMESTAMP", LogConfig::c_Timestamp)
310 class_<LogConfig>(
"LogConfig",
311 R
"(Defines logging settings (log levels and items included in each message) for a certain context, e.g. a module or package.
313 .. seealso:: `logging.package(str) <basf2.LogPythonInterface.package>`)")
314 .def(init<bp::optional<LogConfig::ELogLevel, int> >())
315 .add_property("log_level", &LogConfig::getLogLevel, &LogConfig::setLogLevel,
"set or get the current log level")
316 .add_property(
"debug_level", &LogConfig::getDebugLevel, &LogConfig::setDebugLevel,
"set or get the current debug level")
317 .add_property(
"abort_level", &LogConfig::getAbortLevel, &LogConfig::setAbortLevel,
318 "set or get the severity which causes program abort")
319 .def(
"set_log_level", &LogConfig::setLogLevel, args(
"log_level"), R
"DOC(
320 Set the minimum log level to be shown. Messages with a log level below this value will not be shown at all.
322 .. warning: Message with a level of `ERROR <LogLevel.ERROR>` or higher will always be shown and cannot be silenced.
324 .def("set_debug_level", &LogConfig::setDebugLevel, args(
"debug_level"), R
"DOC(
325 Set the maximum debug level to be shown. Any messages with log level `DEBUG <LogLevel.DEBUG>` and a larger debug level will not be shown.
327 .. seealso: the documentation of `DEBUG <LogLevel.DEBUG>` for suitable values
329 .def("set_abort_level", &LogConfig::setAbortLevel, args(
"abort_level"), R
"DOC(
330 Set the severity which causes program abort.
332 This can be set to a `LogLevel` which will cause the processing to be aborted if
333 a message with the given level or higher is encountered. The default is
334 `FATAL <LogLevel.FATAL>`. It cannot be set any higher but can be lowered.
336 .def("set_info", &LogConfig::setLogInfo, args(
"log_level",
"log_info"),
337 "set the bitmask of LogInfo members to show when printing messages for a given log level")
338 .def(
"get_info", &LogConfig::getLogInfo, args(
"log_level"),
339 "get the current bitmask of which parts of the log message will be printed for a given log level")
345 class_<LogPythonInterface, std::shared_ptr<LogPythonInterface>, boost::noncopyable>(
"LogPythonInterface", R
"(
346 Logging configuration (for messages generated from C++ or Python), available as a global `basf2.logging` object in Python. See also `basf2.set_log_level()` and `basf2.set_debug_level()`.
348 This class exposes a object called `logging <basf2.logging>` to the python interface. With
349 this object it is possible to set all properties of the logging system
350 directly in the steering file in a consistent manner This class also
351 exposes the `LogConfig` class as well as the `LogLevel`
352 and `LogInfo` enums to make setting of properties more transparent
353 by using the names and not just the values. To set or get the log level,
356 >>> logging.log_level = LogLevel.FATAL
357 >>> print("Logging level set to", logging.log_level)
360 This module also allows to send log messages directly from python to ease
361 consistent error reporting throughout the framework
363 >>> B2WARNING("This is a warning message")
367 For all features, see :download:`b2logging.py </framework/examples/b2logging.py>`)")
368 .add_property("log_level", &LogPythonInterface::getLogLevel, &LogPythonInterface::setLogLevel, R
"DOC(
369 Attribute for setting/getting the current `log level <basf2.LogLevel>`.
370 Messages with a lower level are ignored.
372 .. warning: Message with a level of `ERROR <LogLevel.ERROR>` or higher will always be shown and cannot be silenced.
374 .add_property("debug_level", &LogPythonInterface::getDebugLevel, &LogPythonInterface::setDebugLevel,
375 "Attribute for getting/setting the debug level. If debug messages are enabled, their level needs to be at least this high to be printed. Defaults to 100.")
376 .add_property(
"abort_level", &LogPythonInterface::getAbortLevel, &LogPythonInterface::setAbortLevel,
377 "Attribute for setting/getting the `log level <basf2.LogLevel>` at which to abort processing. Defaults to `FATAL <LogLevel.FATAL>` but can be set to a lower level in rare cases.")
378 .add_property(
"max_repetitions", &LogPythonInterface::getMaxMessageRepetitions, &LogPythonInterface::setMaxMessageRepetitions, R
"DOC(
379 Set the maximum amount of times log messages with the same level and message text
380 (excluding variables) will be repeated before it is suppressed. Suppressed messages
381 will still be counted but not shown for the remainder of the processing.
383 This affects messages with the same text but different ref:`logging_logvariables`.
384 If the same log message is repeated frequently with different variables all of
385 these will be suppressed after the given amount of repetitions.
387 .. versionadded:: release-05-00-00
390 .def("set_package", &LogPythonInterface::setPackageLogConfig, args(
"package",
"config"),
391 "Set `basf2.LogConfig` for given package, see also `package() <basf2.LogPythonInterface.package>`.")
392 .def(
"package", &LogPythonInterface::getPackageLogConfig, return_value_policy<reference_existing_object>(), args(
"package"),
393 R
"(Get the `LogConfig` for given package to set detailed logging pararameters for this package.
395 >>> logging.package('svd').debug_level = 10
396 >>> logging.package('svd').set_info(LogLevel.INFO, LogInfo.LEVEL | LogInfo.MESSAGE | LogInfo.FILE)
398 .def("set_info", &LogPythonInterface::setLogInfo, args(
"log_level",
"log_info"),
399 R
"DOCSTRING(Set info to print for given log level. Should be an OR combination of `basf2.LogInfo` constants.
400 As an example, to show only the level and text for all debug messages one could use
402 >>> basf2.logging.set_info(basf2.LogLevel.DEBUG, basf2.LogInfo.LEVEL | basf2.LogInfo.MESSAGE)
405 log_level (LogLevel): log level for which to set the display info
406 log_info (int): Bitmask of `basf2.LogInfo` constants.)DOCSTRING")
407 .def("get_info", &LogPythonInterface::getLogInfo, args(
"log_level"),
"Get info to print for given log level.\n\n"
408 "Parameters:\n log_level (basf2.LogLevel): Log level for which to get the display info")
409 .def(
"add_file", &LogPythonInterface::addLogFile, (bp::arg(
"filename"), bp::arg(
"append") =
false),
410 R
"DOCSTRING(Write log output to given file. (In addition to existing outputs)\n\n"
413 filename (str): Filename to to write log messages into
414 append (bool): If set to True the file will be truncated before writing new messages.)DOCSTRING")
415 .def("add_console", addLogConsole,
416 addLogConsole_overloads(args(
"enable_color"),
"Write log output to console. (In addition to existing outputs). "
417 "If ``enable_color`` is not specified color will be enabled if supported"))
418 .def(
"add_json", &LogPythonInterface::addLogJSON, (bp::arg(
"complete_info") =
false), R
"DOCSTRING(
419 Write log output to console, but format log messages as json objects for
420 simplified parsing by other tools. Each log message will be printed as a one
423 .. versionadded:: release-03-00-00
426 complete_info (bool): If this is set to True the complete log information is printed regardless of the `LogInfo` setting.
429 `add_console()`, `set_info()`
431 .def("add_udp", &LogPythonInterface::addLogUDP, (bp::arg(
"hostname"), bp::arg(
"port")), R
"DOCSTRING(
432 Send the log output as a JSON object to the given hostname and port via UDP.
434 .. versionadded:: release-04-00-00
437 hostname (str): The hostname to send the message to. If it can not be resolved, an exception will be thrown.
438 port (int): The port on the host to send the message via UDP.
443 .def("terminal_supports_colors", &terminalSupportsColors,
"Returns true if the terminal supports colored output")
444 .staticmethod(
"terminal_supports_colors")
445 .def(
"reset", &LogPythonInterface::reset,
"Remove all configured logging outputs. "
446 "You can then configure your own via `add_file() <basf2.LogPythonInterface.add_file>` "
447 "or `add_console() <basf2.LogPythonInterface.add_console>`")
448 .def(
"zero_counters", &LogPythonInterface::zeroCounters,
"Reset the per-level message counters.")
449 .def_readonly(
"log_stats", &LogPythonInterface::getLogStatistics,
"Returns dictionary with message counters.")
450 .def(
"enable_summary", &LogPythonInterface::enableErrorSummary, args(
"on"),
451 "Enable or disable the error summary printed at the end of processing. "
452 "Expects one argument whether or not the summary should be shown")
453 .add_property(
"enable_python_logging", &LogPythonInterface::getPythonLoggingEnabled,
454 &LogPythonInterface::setPythonLoggingEnabled, R
"DOCSTRING(
455 Enable or disable logging via python. If this is set to true than log messages
456 will be sent via `sys.stdout`. This is probably slightly slower but is useful
457 when running in jupyter notebooks or when trying to redirect stdout in python
458 to a buffer. This setting affects all log connections to the
461 .. versionadded:: release-03-00-00)DOCSTRING")
462 .add_property("enable_escape_newlines", &LogPythonInterface::getEscapeNewlinesEnabled,
463 &LogPythonInterface::setEscapeNewlinesEnabled, R
"DOCSTRING(
464 Enable or disable escaping of newlines in log messages to the console. If this
465 is set to true than any newline character in log messages printed to the console
466 will be replaced by a "\n" to ensure that every log messages fits exactly on one line.
468 .. versionadded:: release-04-02-00)DOCSTRING")
473 scope().attr(
"logging") = initguard;
480 const std::string common_doc = R
"DOCSTRING(
481 All additional positional arguments are converted to strings and concatenated
482 to the log message. All keyword arguments are added to the function as
483 :ref:`logging_logvariables`.)DOCSTRING";
485 auto logDebug = raw_function(&LogPythonInterface::logDebug);
486 def(
"B2DEBUG", logDebug);
487 setattr(logDebug,
"__doc__",
"B2DEBUG(debugLevel, message, *args, **kwargs)\n\n"
488 "Print a `DEBUG <basf2.LogLevel.DEBUG>` message. "
489 "The first argument is the `debug_level <basf2.LogLevel.DEBUG>`. " +
492 auto logInfo = raw_function(&LogPythonInterface::logInfo);
493 def(
"B2INFO", logInfo);
494 setattr(logInfo,
"__doc__",
"B2INFO(message, *args, **kwargs)\n\n"
495 "Print a `INFO <basf2.LogLevel.INFO>` message. " + common_doc);
497 auto logResult = raw_function(&LogPythonInterface::logResult);
498 def(
"B2RESULT", logResult);
499 setattr(logResult,
"__doc__",
"B2RESULT(message, *args, **kwargs)\n\n"
500 "Print a `RESULT <basf2.LogLevel.RESULT>` message. " + common_doc
501 +
"\n\n.. deprecated:: release-01-00-00\n use `B2INFO()` instead");
503 auto logWarning = raw_function(&LogPythonInterface::logWarning);
504 def(
"B2WARNING", logWarning);
505 setattr(logWarning,
"__doc__",
"B2WARNING(message, *args, **kwargs)\n\n"
506 "Print a `WARNING <basf2.LogLevel.WARNING>` message. " + common_doc);
508 auto logError = raw_function(&LogPythonInterface::logError);
509 def(
"B2ERROR", logError);
510 setattr(logError,
"__doc__",
"B2ERROR(message, *args, **kwargs)\n\n"
511 "Print a `ERROR <basf2.LogLevel.ERROR>` message. " + common_doc);
513 auto logFatal = raw_function(&LogPythonInterface::logFatal);
514 def(
"B2FATAL", logFatal);
515 setattr(logFatal,
"__doc__",
"B2FATAL(message, *args, **kwargs)\n\n"
516 "Print a `FATAL <basf2.LogLevel.FATAL>` message. " + common_doc +
517 "\n\n.. note:: This also exits the programm with an error and is "
518 "guaranteed to not return.");
523 std::string pythonObjectToString(
const boost::python::object& obj)
525 return boost::python::extract<std::string>(obj.attr(
"__str__")());
532 auto pythonDictToMap(
const dict& d)
534 std::map<std::string, std::string> result;
535 if (d.is_none())
return result;
536 const auto items = d.items();
537 const int size = len(d);
538 for (
int i = 0; i < size; ++i) {
539 const auto key = pythonObjectToString(items[i][0]);
540 const auto val = pythonObjectToString(items[i][1]);
541 result.emplace(std::make_pair(key, val));
551 void dispatchMessage(
LogConfig::ELogLevel logLevel, boost::python::tuple args,
const boost::python::dict& kwargs)
554 const int firstArg = logLevel == LogConfig::c_Debug ? 1 : 0;
555 const int argSize = len(args);
556 if (argSize - firstArg <= 0) {
557 PyErr_SetString(PyExc_TypeError, (
"At least " + std::to_string(firstArg + 1) +
" positional arguments required").c_str());
558 boost::python::throw_error_already_set();
560 if (logLevel == LogConfig::c_Debug) {
561 boost::python::extract<int> proxy(args[0]);
562 if (!proxy.check()) {
563 PyErr_SetString(PyExc_TypeError,
"First argument `debugLevel` must be an integer");
564 boost::python::throw_error_already_set();
571 stringstream message;
572 int size = len(args);
573 for (
int i = firstArg; i < size; ++i) {
574 message << pythonObjectToString(args[i]);
576 const auto cppKwArgs = pythonDictToMap(kwargs);
581 object inspect =
import(
"inspect");
582 auto frame = inspect.attr(
"currentframe")();
583 const std::string
function = extract<std::string>(frame.attr(
"f_code").attr(
"co_name"));
584 const std::string file = extract<std::string>(frame.attr(
"f_code").attr(
"co_filename"));
585 int line = extract<int>(frame.attr(
"f_lineno"));
589 function, file, line, debugLevel));
594 boost::python::object LogPythonInterface::logDebug(boost::python::tuple args,
const boost::python::dict& kwargs)
596 #ifndef LOG_NO_B2DEBUG
597 dispatchMessage(LogConfig::c_Debug, std::move(args), kwargs);
599 return boost::python::object();
602 boost::python::object LogPythonInterface::logInfo(boost::python::tuple args,
const boost::python::dict& kwargs)
604 #ifndef LOG_NO_B2INFO
605 dispatchMessage(LogConfig::c_Info, std::move(args), kwargs);
607 return boost::python::object();
610 boost::python::object LogPythonInterface::logResult(boost::python::tuple args,
const boost::python::dict& kwargs)
612 #ifndef LOG_NO_B2RESULT
613 dispatchMessage(LogConfig::c_Result, std::move(args), kwargs);
615 return boost::python::object();
618 boost::python::object LogPythonInterface::logWarning(boost::python::tuple args,
const boost::python::dict& kwargs)
620 #ifndef LOG_NO_B2WARNING
621 dispatchMessage(LogConfig::c_Warning, std::move(args), kwargs);
623 return boost::python::object();
626 boost::python::object LogPythonInterface::logError(boost::python::tuple args,
const boost::python::dict& kwargs)
628 dispatchMessage(LogConfig::c_Error, std::move(args), kwargs);
629 return boost::python::object();
632 boost::python::object LogPythonInterface::logFatal(boost::python::tuple args,
const boost::python::dict& kwargs)
634 dispatchMessage(LogConfig::c_Fatal, std::move(args), kwargs);
636 return boost::python::object();
ELogLevel
Definition of the supported log levels.
Implements a log connection to an IO Stream.
Implements a log connection that filters repeated messages.
Implements a log connection to stdout but with messages formatted as json objects to allow easy parsi...
Implements a log connection to a text file.
Log Connection to send the log message as JSON to a UDP server.
Thin wrapper to expose a usable interface to the logging framework in python.
Class for logging debug, info and error messages.
bool sendMessage(LogMessage &&message)
Sends a log message using the log connection object.
int getMessageCounter(LogConfig::ELogLevel logLevel) const
Returns the number of logging calls per log level.
static LogSystem & Instance()
Static method to get a reference to the LogSystem instance.
Specialized implementation of an ostream-like class where the << operator can be used to insert value...
Abstract base class for different kinds of events.