diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 65652dd3053b6525607f817ad01d83dd1d3ef8f2..78d60bb5ae408ea843a3966fdba462b7fe7d0cb6 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/sources.sgml,v 2.29 2007/11/07 13:12:21 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/sources.sgml,v 2.30 2008/03/24 18:08:47 tgl Exp $ -->
 
  <chapter id="source">
   <title>PostgreSQL Coding Conventions</title>
@@ -158,6 +158,17 @@ ereport(ERROR,
      <function>errmsg</>.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     <function>errdetail_log(const char *msg, ...)</function> is the same as
+     <function>errdetail</> except that this string goes only to the server
+     log, never to the client.  If both <function>errdetail</> and
+     <function>errdetail_log</> are used then one string goes to the client
+     and the other to the log.  This is useful for error details that are
+     too security-sensitive or too bulky to include in the report
+     sent to the client.
+    </para>
+   </listitem>
    <listitem>
     <para>
      <function>errhint(const char *msg, ...)</function> supplies an optional
diff --git a/src/backend/nls.mk b/src/backend/nls.mk
index c336d60d7e19ecf56b859477b0a38cc28bb0b28e..99e7e9f90a852deab9673bf77d087924977653d2 100644
--- a/src/backend/nls.mk
+++ b/src/backend/nls.mk
@@ -1,10 +1,10 @@
-# $PostgreSQL: pgsql/src/backend/nls.mk,v 1.21 2008/01/30 11:05:37 petere Exp $
+# $PostgreSQL: pgsql/src/backend/nls.mk,v 1.22 2008/03/24 18:08:47 tgl Exp $
 CATALOG_NAME	:= postgres
 AVAIL_LANGUAGES	:= af cs de es fr hr hu it ko nb nl pt_BR ro ru sk sl sv tr zh_CN zh_TW
 GETTEXT_FILES	:= + gettext-files
 # you can add "elog:2" and "errmsg_internal" to this list if you want to
 # include internal messages in the translation list.
-GETTEXT_TRIGGERS:= _ errmsg errdetail errhint errcontext write_stderr yyerror
+GETTEXT_TRIGGERS:= _ errmsg errdetail errdetail_log errhint errcontext write_stderr yyerror
 
 gettext-files: distprep
 	find $(srcdir)/ $(srcdir)/../port/ -name '*.c' -print >$@
diff --git a/src/backend/port/ipc_test.c b/src/backend/port/ipc_test.c
index fa5066fd7e8a09d03a0e03d1cbeb909d2a0500f4..c4a6f487db5cc2aa8a8780ea2fd9d6365cad3fd8 100644
--- a/src/backend/port/ipc_test.c
+++ b/src/backend/port/ipc_test.c
@@ -21,7 +21,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/port/ipc_test.c,v 1.23 2008/01/01 19:45:51 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/port/ipc_test.c,v 1.24 2008/03/24 18:08:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -185,6 +185,13 @@ errdetail(const char *fmt,...)
 	return 0;					/* return value does not matter */
 }
 
+int
+errdetail_log(const char *fmt,...)
+{
+	fprintf(stderr, "DETAIL: %s\n", fmt);
+	return 0;					/* return value does not matter */
+}
+
 int
 errhint(const char *fmt,...)
 {
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 8ff4c2fb67850a24a2a72cb562fac8a125038215..d21ca068b0b92d2ae84e87767c576aeb5e82dfdf 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -42,7 +42,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.202 2008/03/10 12:55:13 mha Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.203 2008/03/24 18:08:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -382,6 +382,8 @@ errfinish(int dummy,...)
 		pfree(edata->message);
 	if (edata->detail)
 		pfree(edata->detail);
+	if (edata->detail_log)
+		pfree(edata->detail_log);
 	if (edata->hint)
 		pfree(edata->hint);
 	if (edata->context)
@@ -700,6 +702,27 @@ errdetail(const char *fmt,...)
 }
 
 
+/*
+ * errdetail_log --- add a detail_log error message text to the current error
+ */
+int
+errdetail_log(const char *fmt,...)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+	MemoryContext oldcontext;
+
+	recursion_depth++;
+	CHECK_STACK_DEPTH();
+	oldcontext = MemoryContextSwitchTo(ErrorContext);
+
+	EVALUATE_MESSAGE(detail_log, false);
+
+	MemoryContextSwitchTo(oldcontext);
+	recursion_depth--;
+	return 0;					/* return value does not matter */
+}
+
+
 /*
  * errhint --- add a hint error message text to the current error
  */
@@ -1010,6 +1033,8 @@ CopyErrorData(void)
 		newedata->message = pstrdup(newedata->message);
 	if (newedata->detail)
 		newedata->detail = pstrdup(newedata->detail);
+	if (newedata->detail_log)
+		newedata->detail_log = pstrdup(newedata->detail_log);
 	if (newedata->hint)
 		newedata->hint = pstrdup(newedata->hint);
 	if (newedata->context)
@@ -1033,6 +1058,8 @@ FreeErrorData(ErrorData *edata)
 		pfree(edata->message);
 	if (edata->detail)
 		pfree(edata->detail);
+	if (edata->detail_log)
+		pfree(edata->detail_log);
 	if (edata->hint)
 		pfree(edata->hint);
 	if (edata->context)
@@ -1103,6 +1130,8 @@ ReThrowError(ErrorData *edata)
 		newedata->message = pstrdup(newedata->message);
 	if (newedata->detail)
 		newedata->detail = pstrdup(newedata->detail);
+	if (newedata->detail_log)
+		newedata->detail_log = pstrdup(newedata->detail_log);
 	if (newedata->hint)
 		newedata->hint = pstrdup(newedata->hint);
 	if (newedata->context)
@@ -1790,8 +1819,11 @@ write_csvlog(ErrorData *edata)
 	appendCSVLiteral(&buf, edata->message);
 	appendStringInfoCharMacro(&buf, ',');
 
-	/* errdetail */
-	appendCSVLiteral(&buf, edata->detail);
+	/* errdetail or errdetail_log */
+	if (edata->detail_log)
+		appendCSVLiteral(&buf, edata->detail_log);
+	else
+		appendCSVLiteral(&buf, edata->detail);
 	appendStringInfoCharMacro(&buf, ',');
 
 	/* errhint */
@@ -1907,7 +1939,14 @@ send_message_to_server_log(ErrorData *edata)
 
 	if (Log_error_verbosity >= PGERROR_DEFAULT)
 	{
-		if (edata->detail)
+		if (edata->detail_log)
+		{
+			log_line_prefix(&buf);
+			appendStringInfoString(&buf, _("DETAIL:  "));
+			append_with_tabs(&buf, edata->detail_log);
+			appendStringInfoChar(&buf, '\n');
+		}
+		else if (edata->detail)
 		{
 			log_line_prefix(&buf);
 			appendStringInfoString(&buf, _("DETAIL:  "));
@@ -2157,6 +2196,8 @@ send_message_to_frontend(ErrorData *edata)
 			pq_sendstring(&msgbuf, edata->detail);
 		}
 
+		/* detail_log is intentionally not used here */
+
 		if (edata->hint)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT);
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 78939baa8987fbc7f5be4d862a4b8ae634b02a2b..3b980cae79a462f1d7b0d3419fa2738c4c6a3833 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.91 2008/03/10 12:55:13 mha Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.92 2008/03/24 18:08:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -125,6 +125,12 @@ errdetail(const char *fmt,...)
    the supplied arguments. */
 __attribute__((format(printf, 1, 2)));
 
+extern int
+errdetail_log(const char *fmt,...)
+/* This extension allows gcc to check the format string for consistency with
+   the supplied arguments. */
+__attribute__((format(printf, 1, 2)));
+
 extern int
 errhint(const char *fmt,...)
 /* This extension allows gcc to check the format string for consistency with
@@ -258,6 +264,7 @@ typedef struct ErrorData
 	int			sqlerrcode;		/* encoded ERRSTATE */
 	char	   *message;		/* primary error message */
 	char	   *detail;			/* detail error message */
+	char	   *detail_log;		/* detail error message for server log only */
 	char	   *hint;			/* hint message */
 	char	   *context;		/* context message */
 	int			cursorpos;		/* cursor index into query string */