Skip to content

Commit b14c10c

Browse files
committed
Translate log_to_postgres ERROR/FATAL into Python exceptions to pass through interpreter
1 parent 87a4bde commit b14c10c

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

python/multicorn/utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,25 @@ def _log_to_postgres(message, level=0, hint=None, detail=None):
2020
}
2121

2222

23+
class MulticornException(Exception):
24+
def __init__(self, message, code, hint, detail):
25+
self._is_multicorn_exception = True
26+
self.message = message
27+
self.code = code
28+
self.hint = hint
29+
self.detail = detail
30+
31+
2332
def log_to_postgres(message, level=INFO, hint=None, detail=None):
2433
code = REPORT_CODES.get(level, None)
2534
if code is None:
2635
raise KeyError("Not a valid log level")
27-
_log_to_postgres(message, code, hint=hint, detail=detail)
28-
36+
if level in (ERROR, CRITICAL):
37+
# if we sent an ERROR or FATAL(=CRITICAL) message to _log_to_postgres, we would trigger the PostgreSQL C-level
38+
# exception handling, which would prevent us from cleanly exiting whatever Python context we're currently in.
39+
# To avoid this, these log levels are replaced with exceptions which are bubbled back to Multicorn's entry
40+
# points, and those exceptions are translated into appropriate logging after we exit the method at the top of
41+
# the multicorn stack.
42+
raise MulticornException(message, code, hint, detail)
43+
else:
44+
_log_to_postgres(message, code, hint=hint, detail=detail)

src/errors.c

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ void reportException(PyObject *pErrType,
1717
PyObject *pErrValue,
1818
PyObject *pErrTraceback);
1919

20+
void reportMulticornException(PyObject *pErrValue);
2021

2122
PGDLLEXPORT void
2223
errorCheck()
@@ -28,7 +29,22 @@ errorCheck()
2829
PyErr_Fetch(&pErrType, &pErrValue, &pErrTraceback);
2930
if (pErrType)
3031
{
31-
reportException(pErrType, pErrValue, pErrTraceback);
32+
// if the error value has a property _is_multicorn_exception and a boolean value True, then we don't report the
33+
// error as a generic exception with a stack trace -- instead we just take the message, code(severity), hint,
34+
// and detail, and log it to Postgres. These exceptions are generated in utils.py to intercept ERROR/FATAL log
35+
// messages. So, first detect whether that's the case, and call a new reporting function...
36+
PyObject *is_multicorn_exception = PyObject_GetAttrString(pErrValue, "_is_multicorn_exception");
37+
if (is_multicorn_exception != NULL && PyObject_IsTrue(is_multicorn_exception))
38+
{
39+
Py_DECREF(is_multicorn_exception);
40+
Py_DECREF(pErrType);
41+
Py_DECREF(pErrTraceback);
42+
reportMulticornException(pErrValue);
43+
}
44+
else
45+
{
46+
reportException(pErrType, pErrValue, pErrTraceback);
47+
}
3248
}
3349
}
3450

@@ -92,3 +108,64 @@ reportException(PyObject *pErrType, PyObject *pErrValue, PyObject *pErrTraceback
92108
errfinish(0);
93109
#endif
94110
}
111+
112+
void reportMulticornException(PyObject* pErrValue)
113+
{
114+
int severity;
115+
PyObject *message = PyObject_GetAttrString(pErrValue, "message");
116+
PyObject *hint = PyObject_GetAttrString(pErrValue, "hint");
117+
PyObject *detail = PyObject_GetAttrString(pErrValue, "detail");
118+
PyObject *code = PyObject_GetAttrString(pErrValue, "code");
119+
int level = PyLong_AsLong(code);
120+
121+
// Matches up with REPORT_CODES in utils.py
122+
switch (level)
123+
{
124+
case 3:
125+
severity = ERROR;
126+
break;
127+
default:
128+
case 4:
129+
severity = FATAL;
130+
break;
131+
}
132+
133+
PG_TRY();
134+
{
135+
136+
#if PG_VERSION_NUM >= 130000
137+
if (errstart(severity, TEXTDOMAIN))
138+
#else
139+
if (errstart(severity, __FILE__, __LINE__, PG_FUNCNAME_MACRO, TEXTDOMAIN))
140+
#endif
141+
{
142+
errmsg("%s", PyString_AsString(message));
143+
if (hint != NULL && hint != Py_None)
144+
{
145+
char* hintstr = PyString_AsString(hint);
146+
errhint("%s", hintstr);
147+
}
148+
if (detail != NULL && detail != Py_None)
149+
{
150+
char* detailstr = PyString_AsString(detail);
151+
errdetail("%s", detailstr);
152+
}
153+
#if PG_VERSION_NUM >= 130000
154+
errfinish(__FILE__, __LINE__, PG_FUNCNAME_MACRO);
155+
#else
156+
errfinish(0);
157+
#endif
158+
}
159+
160+
}
161+
PG_CATCH();
162+
{
163+
Py_DECREF(message);
164+
Py_DECREF(hint);
165+
Py_DECREF(detail);
166+
Py_DECREF(code);
167+
Py_DECREF(pErrValue);
168+
PG_RE_THROW();
169+
}
170+
PG_END_TRY();
171+
}

0 commit comments

Comments
 (0)