Tomographer  v5.4
Tomographer C++ Framework Documentation
pylogger.h
1 /* This file is part of the Tomographer project, which is distributed under the
2  * terms of the MIT license.
3  *
4  * The MIT License (MIT)
5  *
6  * Copyright (c) 2016 ETH Zurich, Institute for Theoretical Physics, Philippe Faist
7  * Copyright (c) 2017 Caltech, Institute for Quantum Information and Matter, Philippe Faist
8  *
9  * Permission is hereby granted, free of charge, to any person obtaining a copy
10  * of this software and associated documentation files (the "Software"), to deal
11  * in the Software without restriction, including without limitation the rights
12  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the Software is
14  * furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25  * SOFTWARE.
26  */
27 
28 
29 #ifndef TOMOGRAPHER_PY_PYLOGGER_H
30 #define TOMOGRAPHER_PY_PYLOGGER_H
31 
32 // this is normally not required (circular), because common.h includes this file; yet keep
33 // this in case a user includes pylogger.h without including common.h first.
34 #include <tomographerpy/common.h>
35 
36 #if PY_MAJOR_VERSION < 3
37 // If using Python 2, we need the eval() function internally below -- see toPythonLevel()
38 #include <pybind11/eval.h>
39 #endif
40 
42 
43 
44 namespace tpy {
45  class PyLogger; // forward declaration
46 }
47 namespace Tomographer { namespace Logger {
48  // Traits for PyLogger
49  template<>
50  struct TOMOGRAPHER_EXPORT LoggerTraits<tpy::PyLogger> : DefaultLoggerTraits
51  {
52  enum {
53  // Python calls are not thread safe.
54  IsThreadSafe = 0
55  };
56  };
57 } } // namespaces
58 
59 namespace tpy {
60 
92 class TOMOGRAPHER_EXPORT PyLogger
93  : public Tomographer::Logger::LoggerBase<PyLogger>
94 {
95 private:
96  pybind11::object py_logging; // the Python "logging" module
97  pybind11::object py_logger; // the Python "logging.Logger" instance for our use
98  bool _bypasspython;
99 public:
101  inline PyLogger();
102 
104  inline void initPythonLogger(std::string logger_name = "tomographer");
105 
109  inline void setLevel(int level);
110 
112  inline py::object toPythonLevel(int level) const;
113 
115  inline py::object toPythonLevelName(int level) const;
116 
118  inline int fromPythonLevel(py::object pylvl) const;
119 
120 
122  inline void emitLog(int level, const char * origin, const std::string & msg);
123 
124 
125  // // Tools to bypass a Python call to logger and just write to stderr -- not used so far.
126  // // To be used in a critical situation where it's not a good idea to call Python code
127  // inline void bypassPython() {
128  // _bypasspython = true;
129  // }
130  // inline void endBypassPython() {
131  // _bypasspython = false;
132  // }
133  // struct _BypassPython {
134  // PyLogger *pylogger;
135  // _BypassPython(PyLogger * l) : pylogger(l) {
136  // pylogger->bypassPython();
137  // }
138  // _BypassPython(const _BypassPython & copy) = delete;
139  // _BypassPython(_BypassPython && other) : pylogger(other.pylogger) { other.pylogger = NULL; }
140  // ~_BypassPython() {
141  // if (pylogger != NULL) {
142  // pylogger->endBypassPython();
143  // }
144  // }
145  // };
146  // /**
147  // * \code
148  // * {
149  // * auto dummy = pushBypassPython();
150  // *
151  // * ...
152  // * } // bypass released after "dummy" goes out of scope
153  // * \endcode
154  // */
155  // inline _BypassPython pushBypassPython() {
156  // return _BypassPython(this);
157  // }
158 
159 };
160 
161 
162 inline
164  : Tomographer::Logger::LoggerBase<PyLogger>(),
165  py_logging(),
166  py_logger(),
167  _bypasspython(false)
168 {
169 }
170 
171 
172 inline
174 {
175  py_logging = py::module::import("logging");
176  py_logger = py::getattr(py_logging, "getLogger")(logger_name);
177 
178  // set the level at which messages will be actually seen -- any changes to the Python
179  // logger's level configuration must be manually reported to us as well in order to have
180  // the correct logging level.
181  int lvl = fromPythonLevel(py::getattr(py_logger, "getEffectiveLevel")());
182  setLevel( lvl );
183 
184  this->debug("PyLogger::initPythonLogger", [lvl](std::ostream & stream) {
185  stream << "Initialized python-compatible logging. level = " << Tomographer::Logger::LogLevel(lvl);
186  });
187 }
188 
189 
190 inline
192 {
193  setLogLevel(level);
194  // produce a warning if the level is set to LONGDEBUG but the messages won't display --
195  // this really slows down the computation time and a user could be wondering why
196  if (level == Tomographer::Logger::LONGDEBUG) {
197  if (!py_logger.is_none()) {
198  // but only perform this check if py_logger is not None
199  int effective_level = fromPythonLevel(py::getattr(py_logger, "getEffectiveLevel")());
200  if (effective_level != Tomographer::Logger::LONGDEBUG) {
201  this->warning("PyLogger::setLevel", [&](std::ostream & stream) {
202  stream << "Log level LONGDEBUG set on C++ logger but Python logger only displays messages of "
203  << "severity at least " << Tomographer::Logger::LogLevel(effective_level) << ". This will "
204  << "considerably and uselessly slow down the computation as tons of messages on the "
205  << "C++ side will be emitted to the Python logger (where they will be ignored) instead of "
206  << "being filtered out immediately.";
207  });
208  }
209  }
210  }
211 }
212 
213 inline
214 void PyLogger::emitLog(int level, const char * origin, const std::string & msg)
215 {
216  //fprintf(stderr, "HERE (X)\n");
217  if (_bypasspython) {
218  fprintf(stderr, "%s:%s:%s (bypassed python logger)\n",
219  Tomographer::Logger::LogLevel(level).levelName().c_str(),
220  origin,
221  msg.c_str());
222  return;
223  }
224  if (py_logger.is_none()) {
225  fprintf(stderr,
226  "tomographer:PyLogger: INTERNAL ERROR: PYTHON LOGGING MODULE NOT SET.\n"
227  "In attempt to call emitLog().");
228  fprintf(stderr,
229  "Message was (%d): %s: %s\n\n", level, origin, msg.c_str());
230  } else {
231  //fprintf(stderr, "Emitting log ... (%s)\n", msg.c_str());
232 
233  py::object pylevel = toPythonLevel(level);
234 
235  //DEBUG::: fprintf(stderr, "DEBUG: pylevel = %s\n", py::repr(pylevel).cast<std::string>().c_str());
236 
237  std::string full_msg = std::string("<")+origin+"> "+msg;
238 
239  py::dict extra;
240  extra["origin"] = origin;
241  extra["raw_msg"] = msg;
242  py::dict kwargs;
243  kwargs["extra"] = extra;
244  auto logfn = py::getattr(py_logger, "log");
245  // try {
246  // if (PyErr_Occurred() != NULL || PyErr_CheckSignals() == -1) {
247  // throw py::error_already_set();
248  // }
249  logfn(*py::make_tuple(pylevel, full_msg), **kwargs);
250  // if (PyErr_Occurred() != NULL || PyErr_CheckSignals() == -1) {
251  // throw py::error_already_set();
252  // }
253  }
254 }
255 
256 
257 inline
258 py::object PyLogger::toPythonLevel(int level) const
259 {
260  if (py_logging.is_none()) {
261  fprintf(stderr,
262  "tomographer:PyLogger: INTERNAL ERROR: PYTHON LOGGING MODULE NOT SET.\n"
263  "In attempt to call toPythonLevel().");
264  return py::none();
265  }
266  switch (level) {
268  return py::getattr(py_logging, "ERROR");
270  return py::getattr(py_logging, "WARNING");
272  return py::getattr(py_logging, "INFO");
274  return py::getattr(py_logging, "DEBUG");
276  default:
277 #if PY_MAJOR_VERSION >= 3
278  return py::cast(1); //py::getattr(py_logging, "NOTSET");
279 #else
280  // Returning py::cast(1) above causes an error in Python2 "TypeError: level must be an
281  // integer" ... because it casts the 1 into "1L", a 'long', and not an 'int'!! So
282  // resort to an ugly hack:
283  return py::eval("1");
284 #endif
285  }
286 }
287 
288 inline
289 py::object PyLogger::toPythonLevelName(int level) const
290 {
291  if (py_logging.is_none()) {
292  fprintf(stderr,
293  "tomographer:PyLogger: INTERNAL ERROR: PYTHON LOGGING MODULE NOT SET.\n"
294  "In attempt to call toPythonLevelName().");
295  return py::none();
296  }
297  return py_logging.attr("getLevelName")(toPythonLevel(level));
298 }
299 
300 inline
301 int PyLogger::fromPythonLevel(py::object pylvl) const
302 {
303  if (py_logging.is_none()) {
304  fprintf(stderr,
305  "tomographer:PyLogger: INTERNAL ERROR: PYTHON LOGGING MODULE NOT SET.\n"
306  "In attempt to call fromPythonLevel().");
307  return -1;
308  }
309 
310  if (pylvl.cast<int>() < py::getattr(py_logging, "DEBUG").cast<int>()) {
312  } else if (pylvl.cast<int>() < py::getattr(py_logging, "INFO").cast<int>()) {
314  } else if (pylvl.cast<int>() < py::getattr(py_logging, "WARNING").cast<int>()) {
316  } else if (pylvl.cast<int>() < py::getattr(py_logging, "ERROR").cast<int>()) {
318  } else {
320  }
321 }
322 
323 
324 
325 
326 } // namespace tpy
327 
328 
329 
330 
331 #endif
332 
Base namespace for the Tomographer project.
Definition: densellh.h:45
void setLevel(int level)
Change the level of the current logger. Note that this will NOT automatically change the effective le...
Definition: pylogger.h:191
PyLogger()
Constructor.
Definition: pylogger.h:163
Logger providing transparent integration with Python&#39;s logging module.
Definition: pylogger.h:92
Error logging level.
Definition: loggers.h:83
Object which stores a log level and can initialize from a string.
Definition: loggers.h:158
Base logger class.
Definition: loggers.h:444
py::object toPythonLevelName(int level) const
Convert a Tomographer::Logger level to a Python logging level name (Python string) ...
Definition: pylogger.h:289
int fromPythonLevel(py::object pylvl) const
Convert a logging level to a Tomographer::Logger level constant.
Definition: pylogger.h:301
void setLogLevel(int level)
Store a new run-time log level.
Definition: loggers.h:988
STL class.
Default traits for Logger implementations.
Definition: loggers.h:288
void warning(const char *origin, const char *fmt,...)
emit a warning message
Definition: loggers.h:604
Traits template struct to be specialized for specific Logger implementations.
Definition: loggers.h:351
T c_str(T... args)
Warning logging level.
Definition: loggers.h:92
Long Debug logging level.
Definition: loggers.h:122
C++ Classes and Utilities for Python Modules.
Definition: common.h:89
void debug(const char *origin, const char *fmt,...)
emit an debug message
Definition: loggers.h:710
void emitLog(int level, const char *origin, const std::string &msg)
Callback for actually emitting log messages, don&#39;t call this manually.
Definition: pylogger.h:214
int level() const
Get the log level set for this logger.
Definition: loggers.h:539
STL class.
Debug logging level.
Definition: loggers.h:112
Information logging level.
Definition: loggers.h:100
py::object toPythonLevel(int level) const
Convert a Tomographer::Logger level to a Python logging level (Python integer)
Definition: pylogger.h:258
Utilities for logging messages.
void initPythonLogger(std::string logger_name="tomographer")
Initialize the logger and attach it to a logger in the logging module named logger_name.
Definition: pylogger.h:173