Merge pull request #2618 from ajssmith/amqp1_dev1_branch
[collectd.git] / src / python.c
index cb6698d..64db698 100644 (file)
@@ -233,9 +233,15 @@ static char reg_shutdown_doc[] =
     "The callback function will be called with no parameters except for\n"
     "    data if it was supplied.";
 
+static char CollectdError_doc[] =
+    "Basic exception for collectd Python scripts.\n"
+    "\n"
+    "Throwing this exception will not cause a stacktrace to be logged, \n"
+    "even if LogTraces is enabled in the config.";
+
 static pthread_t main_thread;
 static PyOS_sighandler_t python_sigint_handler;
-static _Bool do_interactive = 0;
+static bool do_interactive;
 
 /* This is our global thread state. Python saves some stuff in thread-local
  * storage. So if we allow the interpreter to run in the background
@@ -244,15 +250,15 @@ static _Bool do_interactive = 0;
 
 static PyThreadState *state;
 
-static PyObject *sys_path, *cpy_format_exception;
+static PyObject *sys_path, *cpy_format_exception, *CollectdError;
 
 static cpy_callback_t *cpy_config_callbacks;
 static cpy_callback_t *cpy_init_callbacks;
 static cpy_callback_t *cpy_shutdown_callbacks;
 
 /* Make sure to hold the GIL while modifying these. */
-static int cpy_shutdown_triggered = 0;
-static int cpy_num_callbacks = 0;
+static int cpy_shutdown_triggered;
+static int cpy_num_callbacks;
 
 static void cpy_destroy_user_data(void *data) {
   cpy_callback_t *c = data;
@@ -300,7 +306,7 @@ static void cpy_build_name(char *buf, size_t size, PyObject *callback,
 }
 
 void cpy_log_exception(const char *context) {
-  int l = 0;
+  int l = 0, collectd_error;
   const char *typename = NULL, *message = NULL;
   PyObject *type, *value, *traceback, *tn, *m, *list;
 
@@ -308,6 +314,7 @@ void cpy_log_exception(const char *context) {
   PyErr_NormalizeException(&type, &value, &traceback);
   if (type == NULL)
     return;
+  collectd_error = PyErr_GivenExceptionMatches(value, CollectdError);
   tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
   m = PyObject_Str(value);                       /* New reference. */
   if (tn != NULL)
@@ -318,11 +325,17 @@ void cpy_log_exception(const char *context) {
     typename = "NamelessException";
   if (message == NULL)
     message = "N/A";
-  Py_BEGIN_ALLOW_THREADS ERROR("Unhandled python exception in %s: %s: %s",
-                               context, typename, message);
-  Py_END_ALLOW_THREADS Py_XDECREF(tn);
+  Py_BEGIN_ALLOW_THREADS;
+  if (collectd_error) {
+    WARNING("%s in %s: %s", typename, context, message);
+  } else {
+    ERROR("Unhandled python exception in %s: %s: %s", context, typename,
+          message);
+  }
+  Py_END_ALLOW_THREADS;
+  Py_XDECREF(tn);
   Py_XDECREF(m);
-  if (!cpy_format_exception || !traceback) {
+  if (!cpy_format_exception || !traceback || collectd_error) {
     PyErr_Clear();
     Py_DECREF(type);
     Py_XDECREF(value);
@@ -356,10 +369,11 @@ void cpy_log_exception(const char *context) {
     if (cpy[strlen(cpy) - 1] == '\n')
       cpy[strlen(cpy) - 1] = 0;
 
-    Py_BEGIN_ALLOW_THREADS ERROR("%s", cpy);
-    Py_END_ALLOW_THREADS
+    Py_BEGIN_ALLOW_THREADS;
+    ERROR("%s", cpy);
+    Py_END_ALLOW_THREADS;
 
-        free(cpy);
+    free(cpy);
   }
 
   Py_XDECREF(list);
@@ -410,9 +424,10 @@ static int cpy_write_callback(const data_set_t *ds,
       PyList_SetItem(
           list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
     } else {
-      Py_BEGIN_ALLOW_THREADS ERROR("cpy_write_callback: Unknown value type %d.",
-                                   ds->ds[i].type);
-      Py_END_ALLOW_THREADS Py_DECREF(list);
+      Py_BEGIN_ALLOW_THREADS;
+      ERROR("cpy_write_callback: Unknown value type %d.", ds->ds[i].type);
+      Py_END_ALLOW_THREADS;
+      Py_DECREF(list);
       CPY_RETURN_FROM_THREADS 0;
     }
     if (PyErr_Occurred() != NULL) {
@@ -433,7 +448,7 @@ static int cpy_write_callback(const data_set_t *ds,
       int64_t si;
       uint64_t ui;
       double d;
-      _Bool b;
+      bool b;
 
       type = meta_data_type(meta, table[i]);
       if (type == MD_TYPE_STRING) {
@@ -688,9 +703,8 @@ static PyObject *cpy_get_dataset(PyObject *self, PyObject *args) {
   for (size_t i = 0; i < ds->ds_num; ++i) {
     tuple = PyTuple_New(4);
     PyTuple_SET_ITEM(tuple, 0, cpy_string_to_unicode_or_bytes(ds->ds[i].name));
-    PyTuple_SET_ITEM(
-        tuple, 1,
-        cpy_string_to_unicode_or_bytes(DS_TYPE_TO_STRING(ds->ds[i].type)));
+    PyTuple_SET_ITEM(tuple, 1, cpy_string_to_unicode_or_bytes(
+                                   DS_TYPE_TO_STRING(ds->ds[i].type)));
     PyTuple_SET_ITEM(tuple, 2, float_or_none(ds->ds[i].min));
     PyTuple_SET_ITEM(tuple, 3, float_or_none(ds->ds[i].max));
     PyList_SET_ITEM(list, i, tuple);
@@ -706,8 +720,10 @@ static PyObject *cpy_flush(PyObject *self, PyObject *args, PyObject *kwds) {
   if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin,
                                   &timeout, NULL, &identifier) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_flush(plugin, timeout, identifier);
-  Py_END_ALLOW_THREADS PyMem_Free(plugin);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_flush(plugin, timeout, identifier);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(plugin);
   PyMem_Free(identifier);
   Py_RETURN_NONE;
 }
@@ -758,8 +774,7 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler,
 
   register_function(buf, handler,
                     &(user_data_t){
-                        .data = c,
-                        .free_func = cpy_destroy_user_data,
+                        .data = c, .free_func = cpy_destroy_user_data,
                     });
 
   ++cpy_num_callbacks;
@@ -802,8 +817,7 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args,
       /* group = */ "python", buf, cpy_read_callback,
       DOUBLE_TO_CDTIME_T(interval),
       &(user_data_t){
-          .data = c,
-          .free_func = cpy_destroy_user_data,
+          .data = c, .free_func = cpy_destroy_user_data,
       });
   ++cpy_num_callbacks;
   return cpy_string_to_unicode_or_bytes(buf);
@@ -843,8 +857,10 @@ static PyObject *cpy_error(PyObject *self, PyObject *args) {
   char *text;
   if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_log(LOG_ERR, "%s", text);
-  Py_END_ALLOW_THREADS PyMem_Free(text);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_log(LOG_ERR, "%s", text);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(text);
   Py_RETURN_NONE;
 }
 
@@ -852,8 +868,10 @@ static PyObject *cpy_warning(PyObject *self, PyObject *args) {
   char *text;
   if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_log(LOG_WARNING, "%s", text);
-  Py_END_ALLOW_THREADS PyMem_Free(text);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_log(LOG_WARNING, "%s", text);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(text);
   Py_RETURN_NONE;
 }
 
@@ -861,8 +879,10 @@ static PyObject *cpy_notice(PyObject *self, PyObject *args) {
   char *text;
   if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_log(LOG_NOTICE, "%s", text);
-  Py_END_ALLOW_THREADS PyMem_Free(text);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_log(LOG_NOTICE, "%s", text);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(text);
   Py_RETURN_NONE;
 }
 
@@ -870,8 +890,10 @@ static PyObject *cpy_info(PyObject *self, PyObject *args) {
   char *text;
   if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_log(LOG_INFO, "%s", text);
-  Py_END_ALLOW_THREADS PyMem_Free(text);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_log(LOG_INFO, "%s", text);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(text);
   Py_RETURN_NONE;
 }
 
@@ -880,8 +902,10 @@ static PyObject *cpy_debug(PyObject *self, PyObject *args) {
   char *text;
   if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
     return NULL;
-  Py_BEGIN_ALLOW_THREADS plugin_log(LOG_DEBUG, "%s", text);
-  Py_END_ALLOW_THREADS PyMem_Free(text);
+  Py_BEGIN_ALLOW_THREADS;
+  plugin_log(LOG_DEBUG, "%s", text);
+  Py_END_ALLOW_THREADS;
+  PyMem_Free(text);
 #endif
   Py_RETURN_NONE;
 }
@@ -1063,13 +1087,14 @@ static int cpy_shutdown(void) {
   }
   PyErr_Print();
 
-  Py_BEGIN_ALLOW_THREADS cpy_unregister_list(&cpy_config_callbacks);
+  Py_BEGIN_ALLOW_THREADS;
+  cpy_unregister_list(&cpy_config_callbacks);
   cpy_unregister_list(&cpy_init_callbacks);
   cpy_unregister_list(&cpy_shutdown_callbacks);
   cpy_shutdown_triggered = 1;
-  Py_END_ALLOW_THREADS
+  Py_END_ALLOW_THREADS;
 
-      if (!cpy_num_callbacks) {
+  if (!cpy_num_callbacks) {
     Py_Finalize();
     return 0;
   }
@@ -1172,9 +1197,8 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
   values = PyTuple_New(ci->values_num); /* New reference. */
   for (int i = 0; i < ci->values_num; ++i) {
     if (ci->values[i].type == OCONFIG_TYPE_STRING) {
-      PyTuple_SET_ITEM(
-          values, i,
-          cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
+      PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(
+                                      ci->values[i].value.string));
     } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
       PyTuple_SET_ITEM(values, i,
                        PyFloat_FromDouble(ci->values[i].value.number));
@@ -1212,7 +1236,7 @@ PyMODINIT_FUNC PyInit_collectd(void) {
 
 static int cpy_init_python(void) {
   PyOS_sighandler_t cur_sig;
-  PyObject *sys;
+  PyObject *sys, *errordict;
   PyObject *module;
 
 #ifdef IS_PY3K
@@ -1239,6 +1263,11 @@ static int cpy_init_python(void) {
   PyType_Ready(&SignedType);
   UnsignedType.tp_base = &PyLong_Type;
   PyType_Ready(&UnsignedType);
+  errordict = PyDict_New();
+  PyDict_SetItemString(
+      errordict, "__doc__",
+      cpy_string_to_unicode_or_bytes(CollectdError_doc)); /* New reference. */
+  CollectdError = PyErr_NewException("collectd.CollectdError", NULL, errordict);
   sys = PyImport_ImportModule("sys"); /* New reference. */
   if (sys == NULL) {
     cpy_log_exception("python initialization");
@@ -1268,6 +1297,9 @@ static int cpy_init_python(void) {
                      (void *)&SignedType); /* Steals a reference. */
   PyModule_AddObject(module, "Unsigned",
                      (void *)&UnsignedType); /* Steals a reference. */
+  Py_XINCREF(CollectdError);
+  PyModule_AddObject(module, "CollectdError",
+                     CollectdError); /* Steals a reference. */
   PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
   PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
   PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
@@ -1330,7 +1362,7 @@ static int cpy_config(oconfig_item_t *ci) {
 #endif
       sfree(encoding);
     } else if (strcasecmp(item->key, "LogTraces") == 0) {
-      _Bool log_traces;
+      bool log_traces;
       if (cf_util_get_boolean(item, &log_traces) != 0) {
         status = 1;
         continue;
@@ -1438,7 +1470,7 @@ static int cpy_config(oconfig_item_t *ci) {
       status = 1;
     }
   }
-  return (status);
+  return status;
 }
 
 void module_register(void) {