29#include "log4cxx/patternlayout.h"
30#include "log4cxx/helpers/stringhelper.h"
37using namespace log4cxx::helpers;
45 GilGuard() : gil_state(PyGILState_Ensure()) {}
47 ~GilGuard() { PyGILState_Release(gil_state); }
50 PyGILState_STATE gil_state;
58 PyErr_Fetch(&ptype.
get(), &pvalue.
get(), &ptraceback.
get());
61 if (pvalue !=
nullptr or ptype !=
nullptr) {
64 exc_msg += PyUnicode_AsUTF8(exc_str);
71unsigned const MAX_LRU_CACHE_SIZE = 32;
81 PyObjectPtr logging(PyImport_ImportModule(
"logging"));
82 if (logging ==
nullptr) {
83 ::reraise(
"ImportError: Failed to import Python logging module");
85 _getLogger = PyObject_GetAttrString(logging,
"getLogger");
86 if (_getLogger ==
nullptr) {
87 ::reraise(
"AttributeError: logging.getLogger method does not exist");
90 PyObjectPtr lsstlog_module(PyImport_ImportModule(
"lsst.log"));
91 if (lsstlog_module ==
nullptr) {
92 ::reraise(
"ImportError: Failed to import lsst.log module");
94 _mdc_class = PyObject_GetAttrString(lsstlog_module,
"MDCDict");
95 if (_mdc_class ==
nullptr) {
96 ::reraise(
"AttributeError: lsst.log.MDCDict class does not exist");
104 log4cxx::helpers::Transcoder::encodeUTF8(event->getLoggerName(), logger_name);
105 int const level =
event->getLevel()->toInt();
106 int const pyLevel = level / 1000;
113 auto cache_iter = _cache.
find(logger_name);
114 if (cache_iter != _cache.
end()) {
116 logger = cache_iter->second.logger;
123 if (logger ==
nullptr) {
125 if (logger_name ==
"root") {
126 logger = PyObject_CallFunction(_getLogger,
nullptr);
128 logger = PyObject_CallFunction(_getLogger,
"s", logger_name.
c_str());
131 if (logger ==
nullptr) {
132 ::reraise(
"Failed to retrieve Python logger \"" + logger_name +
"\"");
136 _cache.
emplace(logger_name, LRUEntry({logger, _lru_age ++}));
137 while (_cache.
size() > ::MAX_LRU_CACHE_SIZE) {
141 [](
const LRUCache::value_type& lhs,
const LRUCache::value_type& rhs) {
142 return lhs.second.age < rhs.second.age;
150 for (
auto& cache_value: _cache) {
152 cache_value.second.age = _lru_age ++;
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");
162 if (not PyObject_IsTrue(py_is_enabled)) {
167 auto& loc =
event->getLocationInformation();
169 if (loc.getFileName() !=
nullptr) {
170 file_name = loc.getFileName();
172 int const lineno = loc.getLineNumber();
178 this->layout->format(msg, event, p);
180 if (not msg.empty() and msg.back() ==
'\n') {
183 log4cxx::helpers::Transcoder::encodeUTF8(msg, message);
185 log4cxx::helpers::Transcoder::encodeUTF8(event->getMessage(), message);
192 PyObjectPtr record(PyObject_CallMethod(logger,
"makeRecord",
"sisisOO",
200 if (record ==
nullptr) {
201 ::reraise(
"Failed to create LogRecord instance");
211 PyObjectPtr mdc(PyObject_GetAttrString(record,
"MDC"));
212 if (mdc ==
nullptr) {
215 mdc = PyObject_CallObject(_mdc_class,
nullptr);
216 if (mdc ==
nullptr) {
217 ::reraise(
"Failed to make MDCDict instance");
220 if (PyObject_SetAttrString(record,
"MDC", mdc) == -1) {
221 ::reraise(
"Failed to set LogRecord MDC attribute");
226 for (
auto& mdc_key:
event->getMDCKeySet()) {
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()));
234 if (PyObject_SetItem(mdc, py_key, py_value) == -1) {
236 ::reraise(
"Failed to update MDC dictionary");
241 PyObjectPtr res(PyObject_CallMethod(logger,
"handle",
"O", record.
get()));
242 if (res ==
nullptr) {
243 ::reraise(
"Logger failed to handle LogRecord");
256 if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR(
"MESSAGEPATTERN"),
257 LOG4CXX_STR(
"messagepattern"))) {
258 setLayout(LayoutPtr(
new PatternLayout(value)));
260 AppenderSkeleton::setOption(option, value);
This class defines special log4cxx appender which "appends" log messages to Python logging.
void close() override
Close this appender instance, this is no-op.
void append(const spi::LoggingEventPtr &event, log4cxx::helpers::Pool &p) override
Forward the event to Python logging.
void setOption(const LogString &option, const LogString &value) override
Handle configuration options.
bool requiresLayout() const override
Returns true if appender "requires" layout to be defined for it.
Smart pointer class for PyObject instances.