11 #include <boost/python.hpp>
13 #include <framework/pybasf2/LogPythonInterface.h>
15 #include <framework/logging/LogConnectionFilter.h>
16 #include <framework/logging/LogConnectionTxtFile.h>
17 #include <framework/logging/LogConnectionJSON.h>
18 #include <framework/logging/LogConnectionUDP.h>
19 #include <framework/logging/LogConnectionConsole.h>
20 #include <framework/logging/LogVariableStream.h>
21 #include <framework/logging/LogSystem.h>
23 #include <framework/core/Environment.h>
32 using namespace boost::python;
37 if (overrideLevel != LogConfig::c_Default)
38 level = overrideLevel;
40 LogSystem::Instance().getLogConfig()->setLogLevel(level);
45 LogSystem::Instance().getLogConfig()->setAbortLevel(level);
48 void LogPythonInterface::setDebugLevel(
int level)
50 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
55 LogSystem::Instance().getLogConfig()->setLogInfo(level, info);
58 void LogPythonInterface::setPackageLogConfig(
const std::string& package,
const LogConfig& config)
60 LogSystem::Instance().addPackageLogConfig(package, config);
63 void LogPythonInterface::setMaxMessageRepetitions(
unsigned repetitions)
65 LogSystem::Instance().setMaxMessageRepetitions(repetitions);
70 return LogSystem::Instance().getLogConfig()->getLogLevel();
75 return LogSystem::Instance().getLogConfig()->getAbortLevel();
78 int LogPythonInterface::getDebugLevel()
80 return LogSystem::Instance().getLogConfig()->getDebugLevel();
85 return LogSystem::Instance().getLogConfig()->getLogInfo(level);
88 LogConfig& LogPythonInterface::getPackageLogConfig(
const std::string& package)
90 return LogSystem::Instance().getPackageLogConfig(package);
93 unsigned LogPythonInterface::getMaxMessageRepetitions()
const
95 return LogSystem::Instance().getMaxMessageRepetitions();
98 void LogPythonInterface::addLogJSON(
bool complete)
103 void LogPythonInterface::addLogUDP(
const std::string& hostname,
unsigned short port)
105 LogSystem::Instance().addLogConnection(
new LogConnectionUDP(hostname, port));
108 void LogPythonInterface::addLogFile(
const std::string& filename,
bool append)
113 void LogPythonInterface::addLogConsole()
118 void LogPythonInterface::addLogConsole(
bool color)
123 void LogPythonInterface::reset()
125 LogSystem::Instance().resetLogConnections();
128 void LogPythonInterface::zeroCounters()
130 LogSystem::Instance().resetMessageCounter();
133 void LogPythonInterface::enableErrorSummary(
bool on)
135 LogSystem::Instance().enableErrorSummary(on);
138 void LogPythonInterface::setPythonLoggingEnabled(
bool enabled)
const
140 LogConnectionConsole::setPythonLoggingEnabled(enabled);
143 bool LogPythonInterface::getPythonLoggingEnabled()
const
145 return LogConnectionConsole::getPythonLoggingEnabled();
148 void LogPythonInterface::setEscapeNewlinesEnabled(
bool enabled)
const
150 LogConnectionConsole::setEscapeNewlinesEnabled(enabled);
153 bool LogPythonInterface::getEscapeNewlinesEnabled()
const
155 return LogConnectionConsole::getEscapeNewlinesEnabled();
159 dict LogPythonInterface::getLogStatistics()
162 const LogSystem& logSys = LogSystem::Instance();
163 for (
int iLevel = 0; iLevel < LogConfig::c_Default; ++iLevel) {
171 #if !defined(__GNUG__) || defined(__ICC)
173 #pragma GCC diagnostic push
174 #pragma GCC diagnostic ignored "-Wunused-local-typedefs"
177 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(addLogConsole_overloads, addLogConsole, 0, 1)
178 #if !defined(__GNUG__) || defined(__ICC)
180 #pragma GCC diagnostic pop
183 bool terminalSupportsColors()
185 return LogConnectionConsole::terminalSupportsColors(STDOUT_FILENO);
190 void LogPythonInterface::exposePythonAPI()
193 namespace bp = boost::python;
195 docstring_options options(
true,
true,
false);
198 enum_<LogConfig::ELogLevel>(
"LogLevel", R
"DOCSTRING(Class for all possible log levels
202 The lowest possible severity meant for expert only information and disabled
203 by default. In contrast to all other log levels DEBUG messages have an
204 additional numeric indication of their priority called the ``debug_level`` to
205 allow for different levels of verbosity.
207 The agreed values for ``debug_level`` are
209 * **0-9** for user code. These numbers are reserved for user analysis code and
210 may not be used by any part of basf2.
211 * **10-19** for analysis package code. The use case is that a user wants to debug
212 problems in analysis jobs with the help of experts.
214 * **20-29** for simulation/reconstruction code.
215 * **30-39** for core framework code.
217 .. note:: The default maximum debug level which will be shown when
218 running ``basf2 --debug`` without any argument for ``--debug`` is **10**
223 Used for informational messages which are of use for the average user but not
224 very important. Should be used very sparsely, everything which is of no
225 interest to the average user should be a debug message.
227 .. attribute:: RESULT
229 Informational message which don't indicate an error condition but are more
230 important than a mere information. For example the calculated cross section
231 or the output file name.
233 .. deprecated:: release-01-00-00
234 use `INFO <basf2.LogLevel.INFO>` messages instead
236 .. attribute:: WARNING
238 For messages which indicate something which is not correct but not fatal to
239 the processing. This should **not** be used to make informational messages
240 more prominent and they should not be ignored by the user but they are not
245 For messages which indicate a clear error condition which needs to be
246 recovered. If error messages are produced before event processing is started
247 the processing will be aborted. During processing errors don't lead to a stop
248 of the processing but still indicate a problem.
252 For errors so severe that no recovery is possible. Emitting a fatal error
253 will always stop the processing and the `B2FATAL` function is guaranteed to
256 .value(LogConfig::logLevelToString(LogConfig::c_Debug), LogConfig::c_Debug)
257 .value(LogConfig::logLevelToString(LogConfig::c_Info), LogConfig::c_Info)
258 .value(LogConfig::logLevelToString(LogConfig::c_Result), LogConfig::c_Result)
259 .value(LogConfig::logLevelToString(LogConfig::c_Warning), LogConfig::c_Warning)
260 .value(LogConfig::logLevelToString(LogConfig::c_Error), LogConfig::c_Error)
261 .value(LogConfig::logLevelToString(LogConfig::c_Fatal), LogConfig::c_Fatal)
262 .value(LogConfig::logLevelToString(LogConfig::c_Default), LogConfig::c_Default)
266 enum_<LogConfig::ELogInfo>(
"LogInfo", R
"DOCSTRING(The different fields of a log message.
268 These fields can be used as a bitmask to configure the appearance of log messages.
272 The severity of the log message, one of `basf2.LogLevel`
274 .. attribute:: MESSAGE
276 The actual log message
278 .. attribute:: MODULE
280 The name of the module active when the message was emitted. Can be empty if
281 no module was active (before/after processing or outside of the normal event
284 .. attribute:: PACKAGE
286 The package the code that emitted the message belongs to. This is empty for
287 messages emitted by python scripts
289 .. attribute:: FUNCTION
291 The function name that emitted the message
295 The filename containing the code emitting the message
299 The line number in the file emitting the message
301 .value("LEVEL", LogConfig::c_Level)
302 .value(
"MESSAGE", LogConfig::c_Message)
303 .value(
"MODULE", LogConfig::c_Module)
304 .value(
"PACKAGE", LogConfig::c_Package)
305 .value(
"FUNCTION", LogConfig::c_Function)
306 .value(
"FILE", LogConfig::c_File)
307 .value(
"LINE", LogConfig::c_Line)
308 .value(
"TIMESTAMP", LogConfig::c_Timestamp)
312 class_<LogConfig>(
"LogConfig",
313 R
"(Defines logging settings (log levels and items included in each message) for a certain context, e.g. a module or package.
315 .. seealso:: `logging.package(str) <basf2.LogPythonInterface.package>`)")
316 .def(init<bp::optional<LogConfig::ELogLevel, int> >())
317 .add_property("log_level", &LogConfig::getLogLevel, &LogConfig::setLogLevel,
"set or get the current log level")
318 .add_property(
"debug_level", &LogConfig::getDebugLevel, &LogConfig::setDebugLevel,
"set or get the current debug level")
319 .add_property(
"abort_level", &LogConfig::getAbortLevel, &LogConfig::setAbortLevel,
320 "set or get the severity which causes program abort")
321 .def(
"set_log_level", &LogConfig::setLogLevel, args(
"log_level"), R
"DOC(
322 Set the minimum log level to be shown. Messages with a log level below this value will not be shown at all.
324 .. warning: Message with a level of `ERROR <LogLevel.ERROR>` or higher will always be shown and cannot be silenced.
326 .def("set_debug_level", &LogConfig::setDebugLevel, args(
"debug_level"), R
"DOC(
327 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.
329 .. seealso: the documentation of `DEBUG <LogLevel.DEBUG>` for suitable values
331 .def("set_abort_level", &LogConfig::setAbortLevel, args(
"abort_level"), R
"DOC(
332 Set the severity which causes program abort.
334 This can be set to a `LogLevel` which will cause the processing to be aborted if
335 a message with the given level or higher is encountered. The default is
336 `FATAL <LogLevel.FATAL>`. It cannot be set any higher but can be lowered.
338 .def("set_info", &LogConfig::setLogInfo, args(
"log_level",
"log_info"),
339 "set the bitmask of LogInfo members to show when printing messages for a given log level")
340 .def(
"get_info", &LogConfig::getLogInfo, args(
"log_level"),
341 "get the current bitmask of which parts of the log message will be printed for a given log level")
347 class_<LogPythonInterface, std::shared_ptr<LogPythonInterface>, boost::noncopyable>(
"LogPythonInterface", R
"(
348 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()`.
350 This class exposes a object called `logging` to the python interface. With
351 this object it is possible to set all properties of the logging system
352 directly in the steering file in a consistent manner This class also
353 exposes the `LogConfig` class as well as the `LogLevel`
354 and `LogInfo` enums to make setting of properties more transparent
355 by using the names and not just the values. To set or get the log level,
358 >>> logging.log_level = LogLevel.FATAL
359 >>> print("Logging level set to", logging.log_level)
362 This module also allows to send log messages directly from python to ease
363 consistent error reporting throughout the framework
365 >>> B2WARNING("This is a warning message")
369 For all features, see :download:`b2logging.py </framework/examples/b2logging.py>`)")
370 .add_property("log_level", &LogPythonInterface::getLogLevel, &LogPythonInterface::setLogLevel, R
"DOC(
371 Attribute for setting/getting the current `log level <basf2.LogLevel>`.
372 Messages with a lower level are ignored.
374 .. warning: Message with a level of `ERROR <LogLevel.ERROR>` or higher will always be shown and cannot be silenced.
376 .add_property("debug_level", &LogPythonInterface::getDebugLevel, &LogPythonInterface::setDebugLevel,
377 "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.")
378 .add_property(
"abort_level", &LogPythonInterface::getAbortLevel, &LogPythonInterface::setAbortLevel,
379 "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.")
380 .add_property(
"max_repetitions", &LogPythonInterface::getMaxMessageRepetitions, &LogPythonInterface::setMaxMessageRepetitions, R
"DOC(
381 Set the maximum amount of times log messages with the same level and message text
382 (excluding variables) will be repeated before it is suppressed. Suppressed messages
383 will still be counted but not shown for the remainder of the processing.
385 This affects messages with the same text but different ref:`logging_logvariables`.
386 If the same log message is repeated frequently with different variables all of
387 these will be suppressed after the given amount of repetitions.
389 .. versionadded:: release-05-00-00
392 .def("set_package", &LogPythonInterface::setPackageLogConfig, args(
"package",
"config"),
393 "Set `basf2.LogConfig` for given package, see also `package() <basf2.LogPythonInterface.package>`.")
394 .def(
"package", &LogPythonInterface::getPackageLogConfig, return_value_policy<reference_existing_object>(), args(
"package"),
395 R
"(Get the `LogConfig` for given package to set detailed logging pararameters for this package.
397 >>> logging.package('svd').debug_level = 10
398 >>> logging.package('svd').set_info(LogLevel.INFO, LogInfo.LEVEL | LogInfo.MESSAGE | LogInfo.FILE)
400 .def("set_info", &LogPythonInterface::setLogInfo, args(
"log_level",
"log_info"),
401 R
"DOCSTRING(Set info to print for given log level. Should be an OR combination of `basf2.LogInfo` constants.
402 As an example, to show only the level and text for all debug messages one could use
404 >>> basf2.logging.set_info(basf2.LogLevel.DEBUG, basf2.LogInfo.LEVEL | basf2.LogInfo.MESSAGE)
407 log_level (LogLevel): log level for which to set the display info
408 log_info (int): Bitmask of `basf2.LogInfo` constants.)DOCSTRING")
409 .def("get_info", &LogPythonInterface::getLogInfo, args(
"log_level"),
"Get info to print for given log level.\n\n"
410 "Parameters:\n log_level (basf2.LogLevel): Log level for which to get the display info")
411 .def(
"add_file", &LogPythonInterface::addLogFile, (bp::arg(
"filename"), bp::arg(
"append") =
false),
412 R
"DOCSTRING(Write log output to given file. (In addition to existing outputs)\n\n"
415 filename (str): Filename to to write log messages into
416 append (bool): If set to True the file will be truncated before writing new messages.)DOCSTRING")
417 .def("add_console", addLogConsole,
418 addLogConsole_overloads(args(
"enable_color"),
"Write log output to console. (In addition to existing outputs). "
419 "If ``enable_color`` is not specified color will be enabled if supported"))
420 .def(
"add_json", &LogPythonInterface::addLogJSON, (bp::arg(
"complete_info") =
false), R
"DOCSTRING(
421 Write log output to console, but format log messages as json objects for
422 simplified parsing by other tools. Each log message will be printed as a one
425 .. versionadded:: release-03-00-00
428 complete_info (bool): If this is set to True the complete log information is printed regardless of the `LogInfo` setting.
431 `add_console()`, `set_info()`
433 .def("add_udp", &LogPythonInterface::addLogUDP, (bp::arg(
"hostname"), bp::arg(
"port")), R
"DOCSTRING(
434 Send the log output as a JSON object to the given hostname and port via UDP.
436 .. versionadded:: release-04-00-00
439 hostname (str): The hostname to send the message to. If it can not be resolved, an exception will be thrown.
440 port (int): The port on the host to send the message via UDP.
445 .def("terminal_supports_colors", &terminalSupportsColors,
"Returns true if the terminal supports colored output")
446 .staticmethod(
"terminal_supports_colors")
447 .def(
"reset", &LogPythonInterface::reset,
"Remove all configured logging outputs. "
448 "You can then configure your own via `add_file() <basf2.LogPythonInterface.add_file>` "
449 "or `add_console() <basf2.LogPythonInterface.add_console>`")
450 .def(
"zero_counters", &LogPythonInterface::zeroCounters,
"Reset the per-level message counters.")
451 .def_readonly(
"log_stats", &LogPythonInterface::getLogStatistics,
"Returns dictionary with message counters.")
452 .def(
"enable_summary", &LogPythonInterface::enableErrorSummary, args(
"on"),
453 "Enable or disable the error summary printed at the end of processing. "
454 "Expects one argument whether or not the summary should be shown")
455 .add_property(
"enable_python_logging", &LogPythonInterface::getPythonLoggingEnabled,
456 &LogPythonInterface::setPythonLoggingEnabled, R
"DOCSTRING(
457 Enable or disable logging via python. If this is set to true than log messages
458 will be sent via `sys.stdout`. This is probably slightly slower but is useful
459 when running in jupyter notebooks or when trying to redirect stdout in python
460 to a buffer. This setting affects all log connections to the
463 .. versionadded:: release-03-00-00)DOCSTRING")
464 .add_property("enable_escape_newlines", &LogPythonInterface::getEscapeNewlinesEnabled,
465 &LogPythonInterface::setEscapeNewlinesEnabled, R
"DOCSTRING(
466 Enable or disable escaping of newlines in log messages to the console. If this
467 is set to true than any newline character in log messages printed to the console
468 will be replaced by a "\n" to ensure that every log messages fits exactly on one line.
470 .. versionadded:: release-04-02-00)DOCSTRING")
475 scope().attr(
"logging") = initguard;
482 const std::string common_doc = R
"DOCSTRING(
483 All additional positional arguments are converted to strings and concatenated
484 to the log message. All keyword arguments are added to the function as
485 :ref:`logging_logvariables`.)DOCSTRING";
487 auto logDebug = raw_function(&LogPythonInterface::logDebug);
488 def(
"B2DEBUG", logDebug);
489 setattr(logDebug,
"__doc__",
"B2DEBUG(debugLevel, message, *args, **kwargs)\n\n"
490 "Print a `DEBUG <basf2.LogLevel.DEBUG>` message. "
491 "The first argument is the `debug_level <basf2.LogLevel.DEBUG>`. " +
494 auto logInfo = raw_function(&LogPythonInterface::logInfo);
495 def(
"B2INFO", logInfo);
496 setattr(logInfo,
"__doc__",
"B2INFO(message, *args, **kwargs)\n\n"
497 "Print a `INFO <basf2.LogLevel.INFO>` message. " + common_doc);
499 auto logResult = raw_function(&LogPythonInterface::logResult);
500 def(
"B2RESULT", logResult);
501 setattr(logResult,
"__doc__",
"B2RESULT(message, *args, **kwargs)\n\n"
502 "Print a `RESULT <basf2.LogLevel.RESULT>` message. " + common_doc
503 +
"\n\n.. deprecated:: release-01-00-00\n use `B2INFO()` instead");
505 auto logWarning = raw_function(&LogPythonInterface::logWarning);
506 def(
"B2WARNING", logWarning);
507 setattr(logWarning,
"__doc__",
"B2WARNING(message, *args, **kwargs)\n\n"
508 "Print a `WARNING <basf2.LogLevel.WARNING>` message. " + common_doc);
510 auto logError = raw_function(&LogPythonInterface::logError);
511 def(
"B2ERROR", logError);
512 setattr(logError,
"__doc__",
"B2ERROR(message, *args, **kwargs)\n\n"
513 "Print a `ERROR <basf2.LogLevel.ERROR>` message. " + common_doc);
515 auto logFatal = raw_function(&LogPythonInterface::logFatal);
516 def(
"B2FATAL", logFatal);
517 setattr(logFatal,
"__doc__",
"B2FATAL(message, *args, **kwargs)\n\n"
518 "Print a `FATAL <basf2.LogLevel.FATAL>` message. " + common_doc +
519 "\n\n.. note:: This also exits the programm with an error and is "
520 "guaranteed to not return.");
525 std::string pythonObjectToString(
const boost::python::object& obj)
527 return boost::python::extract<std::string>(obj.attr(
"__str__")());
534 auto pythonDictToMap(
const dict& d)
536 std::map<std::string, std::string> result;
537 if (d.is_none())
return result;
538 const auto items = d.items();
539 const int size = len(d);
540 for (
int i = 0; i < size; ++i) {
541 const auto key = pythonObjectToString(items[i][0]);
542 const auto val = pythonObjectToString(items[i][1]);
543 result.emplace(std::make_pair(key, val));
553 void dispatchMessage(
LogConfig::ELogLevel logLevel, boost::python::tuple args,
const boost::python::dict& kwargs)
556 const int firstArg = logLevel == LogConfig::c_Debug ? 1 : 0;
557 const int argSize = len(args);
558 if (argSize - firstArg <= 0) {
559 PyErr_SetString(PyExc_TypeError, (
"At least " + std::to_string(firstArg + 1) +
" positional arguments required").c_str());
560 boost::python::throw_error_already_set();
562 if (logLevel == LogConfig::c_Debug) {
563 boost::python::extract<int> proxy(args[0]);
564 if (!proxy.check()) {
565 PyErr_SetString(PyExc_TypeError,
"First argument `debugLevel` must be an integer");
566 boost::python::throw_error_already_set();
573 stringstream message;
574 int size = len(args);
575 for (
int i = firstArg; i < size; ++i) {
576 message << pythonObjectToString(args[i]);
578 const auto cppKwArgs = pythonDictToMap(kwargs);
583 object inspect =
import(
"inspect");
584 auto frame = inspect.attr(
"currentframe")();
585 const std::string
function = extract<std::string>(frame.attr(
"f_code").attr(
"co_name"));
586 const std::string file = extract<std::string>(frame.attr(
"f_code").attr(
"co_filename"));
587 int line = extract<int>(frame.attr(
"f_lineno"));
591 function, file, line, debugLevel));
596 boost::python::object LogPythonInterface::logDebug(boost::python::tuple args,
const boost::python::dict& kwargs)
598 #ifndef LOG_NO_B2DEBUG
599 dispatchMessage(LogConfig::c_Debug, std::move(args), kwargs);
601 return boost::python::object();
604 boost::python::object LogPythonInterface::logInfo(boost::python::tuple args,
const boost::python::dict& kwargs)
606 #ifndef LOG_NO_B2INFO
607 dispatchMessage(LogConfig::c_Info, std::move(args), kwargs);
609 return boost::python::object();
612 boost::python::object LogPythonInterface::logResult(boost::python::tuple args,
const boost::python::dict& kwargs)
614 #ifndef LOG_NO_B2RESULT
615 dispatchMessage(LogConfig::c_Result, std::move(args), kwargs);
617 return boost::python::object();
620 boost::python::object LogPythonInterface::logWarning(boost::python::tuple args,
const boost::python::dict& kwargs)
622 #ifndef LOG_NO_B2WARNING
623 dispatchMessage(LogConfig::c_Warning, std::move(args), kwargs);
625 return boost::python::object();
628 boost::python::object LogPythonInterface::logError(boost::python::tuple args,
const boost::python::dict& kwargs)
630 dispatchMessage(LogConfig::c_Error, std::move(args), kwargs);
631 return boost::python::object();
634 boost::python::object LogPythonInterface::logFatal(boost::python::tuple args,
const boost::python::dict& kwargs)
636 dispatchMessage(LogConfig::c_Fatal, std::move(args), kwargs);
638 return boost::python::object();