mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-03 21:44:30 +08:00
Add RDLog::CaptureErrorLog for capturing error log messages (#9138)
* Add RDLog::CaptureLog for capturing log messages Adds an RAII `CaptureLog` class to `namespace RDLog` (alongside the existing `LogStateSetter`) that redirects an RDKit logger's output to an internal `std::stringstream` for the duration of its lifetime. On destruction the original stream destination and enabled state are fully restored. Nesting is supported: an inner capture shadows the outer one and each collects its own messages independently. The default constructor captures `rdErrorLog`; an explicit constructor accepts any `RDLogger`. Both enable the logger if it was previously disabled and restore that state on destruction. Python bindings expose `rdBase.CaptureLog` as a context manager with a `messages` read-only property, mirroring the existing `rdBase.BlockLogs` pattern. Messages remain accessible after the `with` block exits. C++ tests are added to `catch_logs.cpp` (6 Catch2 sections covering basic capture, empty state, enable/restore, stream restore, explicit logger, and nested captures). Python tests are added to `UnitTestLogging.py` (6 unittest cases covering the same scenarios). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * CaptureLog: add per-level properties (error_messages, warning_messages, etc.) The Python CaptureLog wrapper now captures all four log levels simultaneously. Per-level properties (error_messages, warning_messages, info_messages, debug_messages) give access to messages from each logger independently; the existing messages property returns them all combined. The C++ RDLog::CaptureLog class is unchanged — it remains a clean single-logger RAII type. The Python wrapper composes four instances of it, one per log level. Suggested by bp-kelley in PR review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Refactor CaptureLog: add named per-level subclasses Add CaptureErrorLog, CaptureWarningLog, CaptureInfoLog, and CaptureDebugLog as named convenience subclasses of CaptureLog, each capturing a specific logger. Update Python bindings to expose the four named classes directly (dropping the combined multi-capture approach), and update tests accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Simplify CaptureLog: no argument, captures rdErrorLog only Remove the RDLogger argument overload, the four named subclasses, and the PyCaptureLog template in favor of a single no-argument CaptureLog that mirrors the Schrödinger CaptureRDErrorLog from which it was inspired. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * CaptureLog tests: add dp_dest restoration and LogStateSetter interaction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Rename CaptureLog to CaptureErrorLog The name CaptureLog was ambiguous; CaptureErrorLog is explicit about which logger it captures and avoids redundancy within namespace RDLog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Generalize CaptureErrorLog into CaptureLog with logger parameter Replace CaptureErrorLog with CaptureLog, which accepts any RDLogger in its constructor (e.g. rdErrorLog, rdWarningLog). Add CaptureErrorLog as a convenience subclass that pre-fills rdErrorLog, preserving backward compatibility for existing callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -208,6 +208,25 @@ class BlockLogs : public boost::noncopyable {
|
||||
std::unique_ptr<RDLog::LogStateSetter> m_log_setter;
|
||||
};
|
||||
|
||||
struct PyCaptureErrorLog : boost::noncopyable {
|
||||
PyCaptureErrorLog *enter() { return this; }
|
||||
void exit(python::object /*exc_type*/, python::object /*exc_val*/,
|
||||
python::object /*traceback*/) {
|
||||
if (m_capturer) {
|
||||
m_messages = m_capturer->messages();
|
||||
m_capturer.reset();
|
||||
}
|
||||
}
|
||||
std::string messages() const {
|
||||
return m_capturer ? m_capturer->messages() : m_messages;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RDLog::CaptureErrorLog> m_capturer{
|
||||
new RDLog::CaptureErrorLog};
|
||||
std::string m_messages;
|
||||
};
|
||||
|
||||
namespace {
|
||||
struct python_streambuf_wrapper {
|
||||
typedef boost_adaptbx::python::streambuf wt;
|
||||
@@ -428,4 +447,21 @@ BOOST_PYTHON_MODULE(rdBase) {
|
||||
.def("__enter__", &BlockLogs::enter,
|
||||
python::return_internal_reference<>())
|
||||
.def("__exit__", &BlockLogs::exit);
|
||||
|
||||
python::class_<PyCaptureErrorLog, boost::noncopyable>(
|
||||
"CaptureErrorLog",
|
||||
"Captures messages from rdErrorLog while this instance is in scope.\n"
|
||||
"Can be used as a context manager. The ``messages`` property is\n"
|
||||
"accessible both inside the context and after it exits.\n"
|
||||
"Nesting is supported: inner captures shadow outer ones.\n\n"
|
||||
"Example::\n\n"
|
||||
" with rdBase.CaptureErrorLog() as capture:\n"
|
||||
" rdkit_function_that_may_fail()\n"
|
||||
" print(capture.messages)\n",
|
||||
python::init<>(python::args("self")))
|
||||
.def("__enter__", &PyCaptureErrorLog::enter,
|
||||
python::return_internal_reference<>())
|
||||
.def("__exit__", &PyCaptureErrorLog::exit)
|
||||
.add_property("messages", &PyCaptureErrorLog::messages,
|
||||
"Messages captured from rdErrorLog.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user