diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 9af7ea7292..fd99278766 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -445,3 +445,17 @@ PL/Python function "notice_outerfunc"
                 1
 (1 row)
 
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+  plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+  for row in plpy.cursor(plan):
+    yield row['d']
+$$ LANGUAGE plpython3u;
+SELECT python_error_detail();
+ERROR:  error fetching next item from iterator
+DETAIL:  spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD"
+Value must be an integer.
+CONTEXT:  Traceback (most recent call last):
+PL/Python function "python_error_detail"
diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out
index 7fe864a1a5..9b50c058ac 100644
--- a/src/pl/plpython/expected/plpython_error_5.out
+++ b/src/pl/plpython/expected/plpython_error_5.out
@@ -445,3 +445,17 @@ PL/Python function "notice_outerfunc"
                 1
 (1 row)
 
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+  plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+  for row in plpy.cursor(plan):
+    yield row['d']
+$$ LANGUAGE plpython3u;
+SELECT python_error_detail();
+ERROR:  error fetching next item from iterator
+DETAIL:  spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD"
+Value must be an integer.
+CONTEXT:  Traceback (most recent call last):
+PL/Python function "python_error_detail"
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
index 7c627eacfb..55d4ad689a 100644
--- a/src/pl/plpython/plpy_elog.c
+++ b/src/pl/plpython/plpy_elog.c
@@ -47,6 +47,7 @@ PLy_elog_impl(int elevel, const char *fmt,...)
 	char	   *tbmsg;
 	int			tb_depth;
 	StringInfoData emsg;
+	StringInfoData edtl;
 	PyObject   *exc,
 			   *val,
 			   *tb;
@@ -103,12 +104,20 @@ PLy_elog_impl(int elevel, const char *fmt,...)
 		}
 		primary = emsg.data;
 
-		/* Since we have a format string, we cannot have a SPI detail. */
-		Assert(detail == NULL);
-
-		/* If there's an exception message, it goes in the detail. */
-		if (xmsg)
-			detail = xmsg;
+		/* 
+		 * The detail composed of a message, concatenated with a detail, if
+		 * any, from an underlying exception.
+		 */
+		initStringInfo(&edtl);
+		if (xmsg != NULL)
+			appendStringInfoString(&edtl, xmsg);
+		if (detail != NULL)
+		{
+			if (edtl.len > 0)
+				appendStringInfoChar(&edtl, '\n');
+			appendStringInfoString(&edtl, detail);
+		}
+		detail = edtl.data;
 	}
 	else
 	{
@@ -140,7 +149,10 @@ PLy_elog_impl(int elevel, const char *fmt,...)
 	PG_FINALLY();
 	{
 		if (fmt)
+		{
 			pfree(emsg.data);
+			pfree(edtl.data);
+		}
 		if (xmsg)
 			pfree(xmsg);
 		if (tbmsg)
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 11f14ec5a7..9bb1c0b085 100644
--- a/src/pl/plpython/sql/plpython_error.sql
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -344,3 +344,14 @@ $$ LANGUAGE plpython3u;
 \set SHOW_CONTEXT always
 
 SELECT notice_outerfunc();
+
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+  plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+  for row in plpy.cursor(plan):
+    yield row['d']
+$$ LANGUAGE plpython3u;
+
+SELECT python_error_detail();
