LSST Applications g02d81e74bb+86cf3d8bc9,g180d380827+7a4e862ed4,g2079a07aa2+86d27d4dc4,g2305ad1205+e1ca1c66fa,g29320951ab+012e1474a1,g295015adf3+341ea1ce94,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+c429d67c83,g48712c4677+f88676dd22,g487adcacf7+27e1e21933,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+b41db86c35,g5a732f18d5+53520f316c,g64a986408d+86cf3d8bc9,g858d7b2824+86cf3d8bc9,g8a8a8dda67+585e252eca,g99cad8db69+84912a7fdc,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+a2b54eae19,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+6681f309db,gc120e1dc64+f0fcc2f6d8,gc28159a63d+0e5473021a,gcf0d15dbbd+c429d67c83,gdaeeff99f8+f9a426f77a,ge6526c86ff+0433e6603d,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+86cf3d8bc9,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
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.
 
void close () override
 Close this appender instance, this is no-op.
 
bool requiresLayout () const override
 Returns true if appender "requires" layout to be defined for it.
 
void setOption (const LogString &option, const LogString &value) override
 Handle configuration options.
 

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
This class defines special log4cxx appender which "appends" log messages to Python logging.

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->getLayout()) {
177 LogString msg;
178 this->getLayout()->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: