// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc::log; using namespace isc::util::python; using std::string; using boost::bind; // We encountered a strange problem with Clang (clang version 2.8 // (tags/RELEASE_28 115909)) on OSX, where unwinding the stack // segfaults the moment this exception was thrown and caught. // // Placing it in a named namespace instead of the originalRecommend // unnamed namespace appears to solve this, so as a temporary // workaround, we create a local randomly named namespace here // to solve this issue. namespace clang_unnamed_namespace_workaround { // To propagate python exceptions through our code // This exception is used to signal to the calling function that a // proper Python Exception has already been set, and the caller // should now return NULL. // Since it is only used internally, and should not pass any // information itself, is is not derived from std::exception class InternalError : public std::exception {}; } using namespace clang_unnamed_namespace_workaround; namespace { // This is for testing only. The real module will have it always set as // NULL and will use the global dictionary. MessageDictionary* testDictionary = NULL; PyObject* setTestDictionary(PyObject*, PyObject* args) { PyObject* enableO; // The API doesn't seem to provide conversion to bool, // so we do it little bit manually if (!PyArg_ParseTuple(args, "O", &enableO)) { return (NULL); } int enableI(PyObject_IsTrue(enableO)); if (enableI == -1) { return (NULL); } bool enable(enableI != 0); try { delete testDictionary; testDictionary = NULL; if (enable) { testDictionary = new MessageDictionary; } } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } Py_RETURN_NONE; } PyObject* createMessage(PyObject*, PyObject* args) { const char* mid; const char* text; // We parse the strings if (!PyArg_ParseTuple(args, "ss", &mid, &text)) { return (NULL); } PyObject* origMid; // And extract the original representation of the message // ID, so we can return it instead of creating another instance. // This call shouldn't fail if the previous suceeded. if (!PyArg_ParseTuple(args, "Os", &origMid, &text)) { return (NULL); } try { MessageDictionary* dict = testDictionary ? testDictionary : &MessageDictionary::globalDictionary(); // We ignore the result, they will be in some kind of dupe list // if there's a problem dict->add(mid, text); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } // Return the ID Py_INCREF(origMid); return (origMid); } PyObject* getMessage(PyObject*, PyObject* args) { const char* mid; if (!PyArg_ParseTuple(args, "s", &mid)) { return (NULL); } try { MessageDictionary* dict = testDictionary ? testDictionary : &MessageDictionary::globalDictionary(); const std::string& result(dict->getText(mid)); if (result.empty()) { Py_RETURN_NONE; } else { return (Py_BuildValue("s", result.c_str())); } } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } PyObject* reset(PyObject*, PyObject*) { LoggerManager::reset(); Py_RETURN_NONE; } PyObject* init(PyObject*, PyObject* args, PyObject* arg_keywords) { const char* root; const char* file(NULL); const char* severity("INFO"); bool buffer = false; int dbglevel(0); const char* const keywords[] = { "name", "severity", "debuglevel", "file", "buffer", NULL }; if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb", const_cast(keywords), &root, &severity, &dbglevel, &file, &buffer)) { return (NULL); } try { LoggerManager::init(root, getSeverity(severity), dbglevel, file, buffer); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } Py_RETURN_NONE; } // This initialization is for unit tests. It allows message settings to // be determined by a set of B10_xxx environment variables. (See the // description of initLogger() for more details.) The function has been named // resetUnitTestRootLogger() here as being more descriptive and // trying to avoid confusion. PyObject* resetUnitTestRootLogger(PyObject*, PyObject*) { try { isc::log::resetUnitTestRootLogger(); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } Py_RETURN_NONE; } PyObject* logConfigUpdate(PyObject*, PyObject* args) { // we have no wrappers for ElementPtr and ConfigData, // So we expect JSON strings and convert them. // The new_config object is assumed to have been validated. const char* new_config_json; const char* mod_spec_json; if (!PyArg_ParseTuple(args, "ss", &new_config_json, &mod_spec_json)) { return (NULL); } try { isc::data::ConstElementPtr new_config = isc::data::Element::fromJSON(new_config_json); isc::data::ConstElementPtr mod_spec_e = isc::data::Element::fromJSON(mod_spec_json); isc::config::ModuleSpec mod_spec(mod_spec_e); isc::config::ConfigData config_data(mod_spec); isc::config::default_logconfig_handler("logging", new_config, config_data); Py_RETURN_NONE; } catch (const isc::data::JSONError& je) { std::string error_msg = std::string("JSON format error: ") + je.what(); PyErr_SetString(PyExc_TypeError, error_msg.c_str()); } catch (const isc::data::TypeError&) { PyErr_SetString(PyExc_TypeError, "argument 1 of log_config_update " "is not a map of config data"); } catch (const isc::config::ModuleSpecError&) { PyErr_SetString(PyExc_TypeError, "argument 2 of log_config_update " "is not a correct module specification"); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); } return (NULL); } PyMethodDef methods[] = { {"set_test_dictionary", setTestDictionary, METH_VARARGS, "Set or unset testing mode for message dictionary. In testing, " "the create_message and get_message functions work on different " "than the logger-global dictionary, not polluting it."}, {"create_message", createMessage, METH_VARARGS, "Creates a new message in the dictionary. You shouldn't need to " "call this directly, it should be called by the generated message " "file. Returns the identifier to be used in logging. The text " "shouldn't be empty."}, {"get_message", getMessage, METH_VARARGS, "Get a message. This function is for testing purposes and you don't " "need to call it. It returns None if the message does not exist."}, {"reset", reset, METH_NOARGS, "Reset all logging. For testing purposes only, do not use."}, {"init", reinterpret_cast(init), METH_VARARGS | METH_KEYWORDS, "Run-time initialization. You need to call this before you do any " "logging, to configure the root logger name. You may also provide " "Arguments:\n" "name: root logger name\n" "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or " "'FATAL'\n" "debuglevel (optional): a debug level (integer in the range 0-99) " "file (optional): a file name of a dictionary with message text " "translations\n" "buffer (optional), boolean, when True, causes all log messages " "to be stored internally until log_config_update is called, at " "which point they shall be logged."}, {"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS, "Resets the configuration of the root logger to that set by the " "B10_XXX environment variables. It is aimed at unit tests, where " "the logging is initialized by the code under test; called before " "the unit test starts, this function resets the logging configuration " "to that in use for the C++ unit tests."}, {"log_config_update", logConfigUpdate, METH_VARARGS, "Update logger settings. This method is automatically used when " "ModuleCCSession is initialized with handle_logging_config set " "to True. When called, the first argument is the new logging " "configuration (in JSON format). The second argument is " "the raw specification (as returned from " "ConfigData.get_module_spec().get_full_spec(), and converted to " "JSON format).\n" "Raises a TypeError if either argument is not a (correct) JSON " "string, or if the spec is not a correct spec.\n" "If this call succeeds, the global logger settings have " "been updated." }, {NULL, NULL, 0, NULL} }; class LoggerWrapper : public PyObject { // Everything is public here, as it is accessible only inside this .cc file. public: Logger *logger_; }; extern PyTypeObject logger_type; int Logger_init(PyObject* po_self, PyObject* args, PyObject*) { LoggerWrapper* self = static_cast(po_self); const char* name; if (!PyArg_ParseTuple(args, "s", &name)) { return (-1); } try { self->logger_ = new Logger(name); return (0); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (-1); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (-1); } } void //Logger_destroy(LoggerWrapper* const self) { Logger_destroy(PyObject* po_self) { LoggerWrapper* self = static_cast(po_self); delete self->logger_; self->logger_ = NULL; Py_TYPE(self)->tp_free(self); } // The isc::log doesn't contain function to convert this way const char* severityToText(const Severity& severity) { switch (severity) { case DEFAULT: return ("DEFAULT"); case DEBUG: return ("DEBUG"); case INFO: return ("INFO"); case WARN: return ("WARN"); case ERROR: return ("ERROR"); case FATAL: return ("FATAL"); default: return (NULL); } } PyObject* Logger_getEffectiveSeverity(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); try { return (Py_BuildValue("s", severityToText( self->logger_->getEffectiveSeverity()))); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } PyObject* Logger_getEffectiveDebugLevel(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); try { return (Py_BuildValue("i", self->logger_->getEffectiveDebugLevel())); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } PyObject* Logger_setSeverity(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); const char* severity; int dbgLevel = 0; if (!PyArg_ParseTuple(args, "z|i", &severity, &dbgLevel)) { return (NULL); } try { self->logger_->setSeverity((severity == NULL) ? DEFAULT : getSeverity(severity), dbgLevel); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } Py_RETURN_NONE; } template // Who should remember the pointer-to-method syntax PyObject* Logger_isLevelEnabled(LoggerWrapper* self, FPtr function) { try { if ((self->logger_->*function)()) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } PyObject* Logger_isInfoEnabled(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); return (Logger_isLevelEnabled(self, &Logger::isInfoEnabled)); } PyObject* Logger_isWarnEnabled(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); return (Logger_isLevelEnabled(self, &Logger::isWarnEnabled)); } PyObject* Logger_isErrorEnabled(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); return (Logger_isLevelEnabled(self, &Logger::isErrorEnabled)); } PyObject* Logger_isFatalEnabled(PyObject* po_self, PyObject*) { LoggerWrapper* self = static_cast(po_self); return (Logger_isLevelEnabled(self, &Logger::isFatalEnabled)); } PyObject* Logger_isDebugEnabled(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); int level = MIN_DEBUG_LEVEL; if (!PyArg_ParseTuple(args, "|i", &level)) { return (NULL); } try { if (self->logger_->isDebugEnabled(level)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } string objectToStr(PyObject* object, bool convert) { PyObjectContainer objstr_container; if (convert) { PyObject* text_obj = PyObject_Str(object); if (text_obj == NULL) { // PyObject_Str could fail for various reasons, including because // the object cannot be converted to a string. We exit with // InternalError to preserve the PyErr set in PyObject_Str. throw InternalError(); } objstr_container.reset(text_obj); object = objstr_container.get(); } PyObjectContainer tuple_container(Py_BuildValue("(O)", object)); const char* value; if (!PyArg_ParseTuple(tuple_container.get(), "s", &value)) { throw InternalError(); } return (string(value)); } // Generic function to output the logging message. Called by the real functions. template PyObject* Logger_performOutput(Function function, PyObject* args, bool dbgLevel) { try { const Py_ssize_t number(PyObject_Length(args)); if (number < 0) { return (NULL); } // Which argument is the first to format? const size_t start = dbgLevel ? 2 : 1; if (number < start) { return (PyErr_Format(PyExc_TypeError, "Too few arguments to " "logging call, at least %zu needed and %zd " "given", start, number)); } // Extract the fixed arguments long dbg(0); if (dbgLevel) { PyObjectContainer dbg_container(PySequence_GetItem(args, 0)); dbg = PyLong_AsLong(dbg_container.get()); if (PyErr_Occurred()) { return (NULL); } } // We create the logging message right now. If we fail to convert a // parameter to string, at least the part that we already did will // be output PyObjectContainer msgid_container(PySequence_GetItem(args, start - 1)); const string mid(objectToStr(msgid_container.get(), false)); Logger::Formatter formatter(function(dbg, mid.c_str())); // Now process the rest of parameters, convert each to string and put // into the formatter. It will print itself in the end. for (size_t i(start); i < number; ++ i) { PyObjectContainer param_container(PySequence_GetItem(args, i)); try { formatter = formatter.arg(objectToStr(param_container.get(), true)); } catch (...) { formatter.deactivate(); throw; } } Py_RETURN_NONE; } catch (const InternalError&) { return (NULL); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return (NULL); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); return (NULL); } } // Now map the functions into the performOutput. I wish C++ could do // functional programming. PyObject* Logger_debug(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); return (Logger_performOutput(bind(&Logger::debug, self->logger_, _1, _2), args, true)); } PyObject* Logger_info(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); return (Logger_performOutput(bind(&Logger::info, self->logger_, _2), args, false)); } PyObject* Logger_warn(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); return (Logger_performOutput(bind(&Logger::warn, self->logger_, _2), args, false)); } PyObject* Logger_error(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); return (Logger_performOutput(bind(&Logger::error, self->logger_, _2), args, false)); } PyObject* Logger_fatal(PyObject* po_self, PyObject* args) { LoggerWrapper* self = static_cast(po_self); return (Logger_performOutput(bind(&Logger::fatal, self->logger_, _2), args, false)); } PyMethodDef loggerMethods[] = { { "get_effective_severity", Logger_getEffectiveSeverity, METH_NOARGS, "Returns the effective logging severity as string" }, { "get_effective_debug_level", Logger_getEffectiveDebugLevel, METH_NOARGS, "Returns the current debug level." }, { "set_severity", Logger_setSeverity, METH_VARARGS, "Sets the severity of a logger. The parameters are severity as a " "string and, optionally, a debug level (integer in range 0-99). " "The severity may be NULL, in which case an inherited value is taken." }, { "is_debug_enabled", Logger_isDebugEnabled, METH_VARARGS, "Returns if the logger would log debug message now. " "You can provide a desired debug level." }, { "is_info_enabled", Logger_isInfoEnabled, METH_NOARGS, "Returns if the logger would log info message now." }, { "is_warn_enabled", Logger_isWarnEnabled, METH_NOARGS, "Returns if the logger would log warn message now." }, { "is_error_enabled", Logger_isErrorEnabled, METH_NOARGS, "Returns if the logger would log error message now." }, { "is_fatal_enabled", Logger_isFatalEnabled, METH_NOARGS, "Returns if the logger would log fatal message now." }, { "debug", Logger_debug, METH_VARARGS, "Logs a debug-severity message. It takes the debug level, message ID " "and any number of stringifiable arguments to the message." }, { "info", Logger_info, METH_VARARGS, "Logs a info-severity message. It taskes the message ID and any " "number of stringifiable arguments to the message." }, { "warn", Logger_warn, METH_VARARGS, "Logs a warn-severity message. It taskes the message ID and any " "number of stringifiable arguments to the message." }, { "error", Logger_error, METH_VARARGS, "Logs a error-severity message. It taskes the message ID and any " "number of stringifiable arguments to the message." }, { "fatal", Logger_fatal, METH_VARARGS, "Logs a fatal-severity message. It taskes the message ID and any " "number of stringifiable arguments to the message." }, { NULL, NULL, 0, NULL } }; PyTypeObject logger_type = { PyVarObject_HEAD_INIT(NULL, 0) "isc.log.Logger", sizeof(LoggerWrapper), // tp_basicsize 0, // tp_itemsize Logger_destroy, // tp_dealloc NULL, // tp_print NULL, // tp_getattr NULL, // tp_setattr NULL, // tp_reserved NULL, // tp_repr NULL, // tp_as_number NULL, // tp_as_sequence NULL, // tp_as_mapping NULL, // tp_hash NULL, // tp_call NULL, // tp_str NULL, // tp_getattro NULL, // tp_setattro NULL, // tp_as_buffer Py_TPFLAGS_DEFAULT, // tp_flags "Wrapper around the C++ isc::log::Logger class." "It is not complete, but everything important should be here.", NULL, // tp_traverse NULL, // tp_clear NULL, // tp_richcompare 0, // tp_weaklistoffset NULL, // tp_iter NULL, // tp_iternext loggerMethods, // tp_methods NULL, // tp_members NULL, // tp_getset NULL, // tp_base NULL, // tp_dict NULL, // tp_descr_get NULL, // tp_descr_set 0, // tp_dictoffset Logger_init, // tp_init NULL, // tp_alloc PyType_GenericNew, // tp_new NULL, // tp_free NULL, // tp_is_gc NULL, // tp_bases NULL, // tp_mro NULL, // tp_cache NULL, // tp_subclasses NULL, // tp_weaklist NULL, // tp_del 0 // tp_version_tag }; PyModuleDef iscLog = { { PyObject_HEAD_INIT(NULL) NULL, 0, NULL}, "log", "Python bindings for the classes in the isc::log namespace.\n\n" "These bindings are close match to the C++ API, but they are not complete " "(some parts are not needed) and some are done in more python-like ways.", -1, methods, NULL, NULL, NULL, NULL }; } // end anonymous namespace PyMODINIT_FUNC PyInit_log(void) { PyObject* mod = PyModule_Create(&iscLog); if (mod == NULL) { return (NULL); } // Finalize logger class and add in the definitions of the standard debug // levels. These can then be referred to in Python through the constants // log.DBGLVL_XXX. // N.B. These should be kept in sync with the constants defined in // log_dbglevels.h. try { if (PyType_Ready(&logger_type) < 0) { throw InternalError(); } void* p = &logger_type; if (PyModule_AddObject(mod, "Logger", static_cast(p)) < 0) { throw InternalError(); } installClassVariable(logger_type, "DBGLVL_START_SHUT", Py_BuildValue("I", DBGLVL_START_SHUT)); installClassVariable(logger_type, "DBGLVL_COMMAND", Py_BuildValue("I", DBGLVL_COMMAND)); installClassVariable(logger_type, "DBGLVL_COMMAND_DATA", Py_BuildValue("I", DBGLVL_COMMAND_DATA)); installClassVariable(logger_type, "DBGLVL_TRACE_BASIC", Py_BuildValue("I", DBGLVL_TRACE_BASIC)); installClassVariable(logger_type, "DBGLVL_TRACE_BASIC_DATA", Py_BuildValue("I", DBGLVL_TRACE_BASIC_DATA)); installClassVariable(logger_type, "DBGLVL_TRACE_DETAIL", Py_BuildValue("I", DBGLVL_TRACE_DETAIL)); installClassVariable(logger_type, "DBGLVL_TRACE_DETAIL_DATA", Py_BuildValue("I", DBGLVL_TRACE_DETAIL_DATA)); } catch (const InternalError&) { Py_DECREF(mod); return (NULL); } catch (const std::exception& ex) { const std::string ex_what = "Unexpected failure in Log initialization: " + std::string(ex.what()); PyErr_SetString(PyExc_SystemError, ex_what.c_str()); Py_DECREF(mod); return (NULL); } catch (...) { PyErr_SetString(PyExc_SystemError, "Unexpected failure in Log initialization"); Py_DECREF(mod); return (NULL); } Py_INCREF(&logger_type); return (mod); }