LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
Classes | Public Member Functions | List of all members
lsst::log::detail::PyLogAppender Class Reference

This class defines special log4cxx appender which "appends" log messages to Python logging. More...

#include <PyLogAppender.h>

Inheritance diagram for lsst::log::detail::PyLogAppender:

Public Member Functions

 PyLogAppender ()
 
 PyLogAppender (const PyLogAppender &)=delete
 
PyLogAppenderoperator= (const PyLogAppender &)=delete
 
void append (const spi::LoggingEventPtr &event, log4cxx::helpers::Pool &p) override
 Forward the event to Python logging. More...
 
void close () override
 Close this appender instance, this is no-op. More...
 
bool requiresLayout () const override
 Returns true if appender "requires" layout to be defined for it. More...
 
void setOption (const LogString &option, const LogString &value) override
 Handle configuration options. More...
 

Detailed Description

This class defines special log4cxx appender which "appends" log messages to Python logging.

To use this logger one has to explicitly add it to log4cxx configuration using PyLogAppender as appender class name, for example:

log4j.rootLogger = INFO, PyLog
log4j.appender.PyLog = PyLogAppender
log4j.appender.PyLog.layout = org.apache.log4j.PatternLayout
log4j.appender.PyLog.layout.ConversionPattern = %m (%X{LABEL})
int m
Definition: SpanSet.cc:48

Definition at line 58 of file PyLogAppender.h.

Constructor & Destructor Documentation

◆ PyLogAppender() [1/2]

lsst::log::detail::PyLogAppender::PyLogAppender ( )

Definition at line 77 of file PyLogAppender.cc.

77  {
78 
79  GilGuard gil_guard;
80 
81  PyObjectPtr logging(PyImport_ImportModule("logging"));
82  if (logging == nullptr) {
83  ::reraise("ImportError: Failed to import Python logging module");
84  }
85  _getLogger = PyObject_GetAttrString(logging, "getLogger");
86  if (_getLogger == nullptr) {
87  ::reraise("AttributeError: logging.getLogger method does not exist");
88  }
89 
90  PyObjectPtr lsstlog_module(PyImport_ImportModule("lsst.log"));
91  if (lsstlog_module == nullptr) {
92  ::reraise("ImportError: Failed to import lsst.log module");
93  }
94  _mdc_class = PyObject_GetAttrString(lsstlog_module, "MDCDict");
95  if (_mdc_class == nullptr) {
96  ::reraise("AttributeError: lsst.log.MDCDict class does not exist");
97  }
98 }

◆ PyLogAppender() [2/2]

lsst::log::detail::PyLogAppender::PyLogAppender ( const PyLogAppender )
delete

Member Function Documentation

◆ append()

void lsst::log::detail::PyLogAppender::append ( const spi::LoggingEventPtr &  event,
log4cxx::helpers::Pool &  p 
)
override

Forward the event to Python logging.

Definition at line 100 of file PyLogAppender.cc.

100  {
101 
102  // logger name, UTF-8 encoded
103  std::string logger_name;
104  log4cxx::helpers::Transcoder::encodeUTF8(event->getLoggerName(), logger_name);
105  int const level = event->getLevel()->toInt();
106  int const pyLevel = level / 1000;
107 
108  // Check logger name in cache first, this does not need GIL but needs
109  // synchronization
110  PyObjectPtr logger;
111  {
112  std::lock_guard<std::mutex> lock(_cache_mutex);
113  auto cache_iter = _cache.find(logger_name);
114  if (cache_iter != _cache.end()) {
115  // use it, age is updated later
116  logger = cache_iter->second.logger;
117  }
118  }
119 
120  // Need Python at this point
121  GilGuard gil_guard;
122 
123  if (logger == nullptr) {
124  // was not found in cache get one from Python
125  if (logger_name == "root") {
126  logger = PyObject_CallFunction(_getLogger, nullptr);
127  } else {
128  logger = PyObject_CallFunction(_getLogger, "s", logger_name.c_str());
129  }
130  }
131  if (logger == nullptr) {
132  ::reraise("Failed to retrieve Python logger \"" + logger_name + "\"");
133  } else {
134  // remember it in cache
135  std::lock_guard<std::mutex> lock(_cache_mutex);
136  _cache.emplace(logger_name, LRUEntry({logger, _lru_age ++}));
137  while (_cache.size() > ::MAX_LRU_CACHE_SIZE) {
138  // find oldest element and remove
139  auto iter = std::min_element(
140  _cache.begin(), _cache.end(),
141  [](const LRUCache::value_type& lhs, const LRUCache::value_type& rhs) {
142  return lhs.second.age < rhs.second.age;
143  }
144  );
145  _cache.erase(iter);
146  }
147  // Age counter could potentially overflow (wrap to 0), reset age for
148  // all cache entries to avoid issues.
149  if (_lru_age == 0) {
150  for (auto& cache_value: _cache) {
151  // give them different but "random" ages
152  cache_value.second.age = _lru_age ++;
153  }
154  }
155  }
156 
157  // before doing anything check logging level
158  PyObjectPtr py_is_enabled(PyObject_CallMethod(logger, "isEnabledFor", "i", pyLevel));
159  if (py_is_enabled == nullptr) {
160  ::reraise("Failure when calling logger.isEnabledFor() method");
161  }
162  if (not PyObject_IsTrue(py_is_enabled)) {
163  return;
164  }
165 
166  // collect all necessary info
167  auto& loc = event->getLocationInformation();
168  std::string file_name;
169  if (loc.getFileName() != nullptr) {
170  file_name = loc.getFileName();
171  }
172  int const lineno = loc.getLineNumber();
173 
174  // if layout is defined then use formatted message
175  std::string message;
176  if (this->layout) {
177  LogString msg;
178  this->layout->format(msg, event, p);
179  // get rid of trailing new-line, just in case someone uses %n in format
180  if (not msg.empty() and msg.back() == '\n') {
181  msg.pop_back();
182  }
183  log4cxx::helpers::Transcoder::encodeUTF8(msg, message);
184  } else {
185  log4cxx::helpers::Transcoder::encodeUTF8(event->getMessage(), message);
186  }
187 
188  // record = logger.makeRecord(name, level, fn, lno, msg, args, exc_info,
189  // func=None, extra=None, sinfo=None)
190  // I would like to pass MDC as an `extra` argument but that does not
191  // work reliably in case we want to override record factory.
192  PyObjectPtr record(PyObject_CallMethod(logger, "makeRecord", "sisisOO",
193  logger_name.c_str(),
194  pyLevel,
195  file_name.c_str(),
196  lineno,
197  message.c_str(),
198  Py_None,
199  Py_None));
200  if (record == nullptr) {
201  ::reraise("Failed to create LogRecord instance");
202  }
203 
204  // Record should already have an `MDC` attribute added by a record factory,
205  // and it may be pre-filled with some info by the same factory. Here we
206  // assume that if it already exists then it is dict-like, if it does not
207  // we add this attribute as lsst.log.MDCDict instance (which is a dict with
208  // some extra niceties).
209 
210  // mdc = getattr(record, "MDC")
211  PyObjectPtr mdc(PyObject_GetAttrString(record, "MDC"));
212  if (mdc == nullptr) {
213  PyErr_Clear();
214  // mdc = lsst.log.MDCDict()
215  mdc = PyObject_CallObject(_mdc_class, nullptr);
216  if (mdc == nullptr) {
217  ::reraise("Failed to make MDCDict instance");
218  }
219  // record.MDC = mdc
220  if (PyObject_SetAttrString(record, "MDC", mdc) == -1) {
221  ::reraise("Failed to set LogRecord MDC attribute");
222  }
223  }
224 
225  // Copy MDC to dict, for key in getMDCKeySet(): mdc[key] = event.getMDC(key)
226  for (auto& mdc_key: event->getMDCKeySet()) {
227  std::string key, value;
228  log4cxx::helpers::Transcoder::encodeUTF8(mdc_key, key);
229  log4cxx::LogString mdc_value;
230  event->getMDC(mdc_key, mdc_value);
231  log4cxx::helpers::Transcoder::encodeUTF8(mdc_value, value);
232  PyObjectPtr py_value(PyUnicode_FromStringAndSize(value.data(), value.size()));
233  PyObjectPtr py_key(PyUnicode_FromStringAndSize(key.data(), key.size()));
234  if (PyObject_SetItem(mdc, py_key, py_value) == -1) {
235  // it is probably not a dictionary
236  ::reraise("Failed to update MDC dictionary");
237  }
238  }
239 
240  // logger.handle(record)
241  PyObjectPtr res(PyObject_CallMethod(logger, "handle", "O", record.get()));
242  if (res == nullptr) {
243  ::reraise("Logger failed to handle LogRecord");
244  }
245 }
T begin(T... args)
T c_str(T... args)
T data(T... args)
T emplace(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T lock(T... args)
T min_element(T... args)
T pop_back(T... args)
T size(T... args)

◆ close()

void lsst::log::detail::PyLogAppender::close ( )
override

Close this appender instance, this is no-op.

Definition at line 247 of file PyLogAppender.cc.

247  {
248 }

◆ operator=()

PyLogAppender& lsst::log::detail::PyLogAppender::operator= ( const PyLogAppender )
delete

◆ requiresLayout()

bool lsst::log::detail::PyLogAppender::requiresLayout ( ) const
override

Returns true if appender "requires" layout to be defined for it.

This appender returns false, we use layout but construct it differently.

Definition at line 250 of file PyLogAppender.cc.

250  {
251  return false;
252 }

◆ setOption()

void lsst::log::detail::PyLogAppender::setOption ( const LogString &  option,
const LogString &  value 
)
override

Handle configuration options.

Definition at line 254 of file PyLogAppender.cc.

254  {
255 
256  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("MESSAGEPATTERN"),
257  LOG4CXX_STR("messagepattern"))) {
258  setLayout(LayoutPtr(new PatternLayout(value)));
259  } else {
260  AppenderSkeleton::setOption(option, value);
261  }
262 }

The documentation for this class was generated from the following files: