Hi,

It can be very useful to look at the log messages emitted by a larger number
of postgres instances to see if anything unusual is happening. E.g. checking
whether there are an increased number of internal, IO, corruption errors (and
LOGs too, because we emit plenty bad things as LOG) . One difficulty is that
extensions tend to not categorize their errors. But unfortunately errors in
extensions are hard to distinguish from errors emitted by postgres.

A related issue is that it'd be useful to be able to group log messages by
extension, to e.g. see which extensions are emitting disproportionally many
log messages.

Therefore I'd like to collect the extension name in elog/ereport and add a
matching log_line_prefix escape code.


It's not entirely trivial to provide errfinish() with a parameter indicating
the extension, but it's doable:

1) Have PG_MODULE_MAGIC also define a new variable for the extension name,
   empty at that point

2) In internal_load_library(), look up that new variable, and fill it with a,
   mangled, libname.

4) in elog.h, define a new macro depending on BUILDING_DLL (if it is set,
   we're in the server, otherwise an extension). In the backend itself, define
   it to NULL, otherwise to the variable created by PG_MODULE_MAGIC.

5) In elog/ereport/errsave/... pass this new variable to
   errfinish/errsave_finish.


I've attached a *very rough* prototype of this idea. My goal at this stage was
just to show that it's possible, not for the code to be in a reviewable state.


Here's e.g. what this produces with log_line_prefix='%m [%E] '

2024-05-13 13:50:17.518 PDT [postgres] LOG:  database system is ready to accept 
connections
2024-05-13 13:50:19.138 PDT [cube] ERROR:  invalid input syntax for cube at 
character 13
2024-05-13 13:50:19.138 PDT [cube] DETAIL:  syntax error at or near "f"
2024-05-13 13:50:19.138 PDT [cube] STATEMENT:  SELECT cube('foo');

2024-05-13 13:43:07.484 PDT [postgres] LOG:  database system is ready to accept 
connections
2024-05-13 13:43:11.699 PDT [hstore] ERROR:  syntax error in hstore: unexpected 
end of string at character 15
2024-05-13 13:43:11.699 PDT [hstore] STATEMENT:  SELECT hstore('foo');


It's worth pointing out that this, quite fundamentally, can only work when the
log message is triggered directly by the extension. If the extension code
calls some postgres function and that function then errors out, it'll be seens
as being part of postgres.

But I think that's ok - they're going to be properly errcode-ified etc.


Thoughts?

Greetings,

Andres Freund
>From 1c59c465f2d359e8c47cf91d1ea458ea3b64ec84 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Mon, 13 May 2024 13:47:41 -0700
Subject: [PATCH v1] WIP: Track shared library in which elog/ereport is called

---
 src/include/fmgr.h             |  1 +
 src/include/utils/elog.h       | 18 +++++++++++++-----
 src/backend/utils/error/elog.c | 33 ++++++++++++++++++++++++---------
 src/backend/utils/fmgr/dfmgr.c | 30 ++++++++++++++++++++++++++++++
 4 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ccb4070a251..3c7cfd7fee9 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -504,6 +504,7 @@ PG_MAGIC_FUNCTION_NAME(void) \
 	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
 	return &Pg_magic_data; \
 } \
+PGDLLEXPORT const char *Pg_extension_filename = NULL; \
 extern int no_such_variable
 
 
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 054dd2bf62f..5e57f01823d 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -137,6 +137,13 @@ struct Node;
  * prevents gcc from making the unreachability deduction at optlevel -O0.
  *----------
  */
+#ifdef BUILDING_DLL
+#define ELOG_EXTNAME NULL
+#else
+extern PGDLLEXPORT const char *Pg_extension_filename;
+#define ELOG_EXTNAME Pg_extension_filename
+#endif
+
 #ifdef HAVE__BUILTIN_CONSTANT_P
 #define ereport_domain(elevel, domain, ...)	\
 	do { \
@@ -144,7 +151,7 @@ struct Node;
 		if (__builtin_constant_p(elevel) && (elevel) >= ERROR ? \
 			errstart_cold(elevel, domain) : \
 			errstart(elevel, domain)) \
-			__VA_ARGS__, errfinish(__FILE__, __LINE__, __func__); \
+			__VA_ARGS__, errfinish(__FILE__, __LINE__, __func__, ELOG_EXTNAME); \
 		if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
 			pg_unreachable(); \
 	} while(0)
@@ -154,7 +161,7 @@ struct Node;
 		const int elevel_ = (elevel); \
 		pg_prevent_errno_in_scope(); \
 		if (errstart(elevel_, domain)) \
-			__VA_ARGS__, errfinish(__FILE__, __LINE__, __func__); \
+			__VA_ARGS__, errfinish(__FILE__, __LINE__, __func__, ELOG_EXTNAME); \
 		if (elevel_ >= ERROR) \
 			pg_unreachable(); \
 	} while(0)
@@ -169,7 +176,7 @@ extern bool message_level_is_interesting(int elevel);
 
 extern bool errstart(int elevel, const char *domain);
 extern pg_attribute_cold bool errstart_cold(int elevel, const char *domain);
-extern void errfinish(const char *filename, int lineno, const char *funcname);
+extern void errfinish(const char *filename, int lineno, const char *funcname, const char *extfile);
 
 extern int	errcode(int sqlerrcode);
 
@@ -268,7 +275,7 @@ extern int	getinternalerrposition(void);
 		struct Node *context_ = (context); \
 		pg_prevent_errno_in_scope(); \
 		if (errsave_start(context_, domain)) \
-			__VA_ARGS__, errsave_finish(context_, __FILE__, __LINE__, __func__); \
+			__VA_ARGS__, errsave_finish(context_, __FILE__, __LINE__, __func__, ELOG_EXTNAME); \
 	} while(0)
 
 #define errsave(context, ...)	\
@@ -293,7 +300,7 @@ extern int	getinternalerrposition(void);
 extern bool errsave_start(struct Node *context, const char *domain);
 extern void errsave_finish(struct Node *context,
 						   const char *filename, int lineno,
-						   const char *funcname);
+						   const char *funcname, const char *extname);
 
 
 /* Support for constructing error strings separately from ereport() calls */
@@ -449,6 +456,7 @@ typedef struct ErrorData
 	const char *funcname;		/* __func__ of ereport() call */
 	const char *domain;			/* message domain */
 	const char *context_domain; /* message domain for context message */
+	const char *extname;		/* extension filename or NULL */
 	int			sqlerrcode;		/* encoded ERRSTATE */
 	char	   *message;		/* primary error message (translated) */
 	char	   *detail;			/* detail error message */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index d91a85cb2d7..1d34e89d7f5 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -180,7 +180,7 @@ static ErrorData *get_error_stack_entry(void);
 static void set_stack_entry_domain(ErrorData *edata, const char *domain);
 static void set_stack_entry_location(ErrorData *edata,
 									 const char *filename, int lineno,
-									 const char *funcname);
+									 const char *funcname, const char *extname);
 static bool matches_backtrace_functions(const char *funcname);
 static pg_noinline void set_backtrace(ErrorData *edata, int num_skip);
 static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str);
@@ -474,7 +474,7 @@ errstart(int elevel, const char *domain)
  * return to the caller.  See elog.h for the error level definitions.
  */
 void
-errfinish(const char *filename, int lineno, const char *funcname)
+errfinish(const char *filename, int lineno, const char *funcname, const char *extname)
 {
 	ErrorData  *edata = &errordata[errordata_stack_depth];
 	int			elevel;
@@ -485,7 +485,7 @@ errfinish(const char *filename, int lineno, const char *funcname)
 	CHECK_STACK_DEPTH();
 
 	/* Save the last few bits of error state into the stack entry */
-	set_stack_entry_location(edata, filename, lineno, funcname);
+	set_stack_entry_location(edata, filename, lineno, funcname, extname);
 
 	elevel = edata->elevel;
 
@@ -683,7 +683,7 @@ errsave_start(struct Node *context, const char *domain)
  */
 void
 errsave_finish(struct Node *context, const char *filename, int lineno,
-			   const char *funcname)
+			   const char *funcname, const char *extname)
 {
 	ErrorSaveContext *escontext = (ErrorSaveContext *) context;
 	ErrorData  *edata = &errordata[errordata_stack_depth];
@@ -697,7 +697,7 @@ errsave_finish(struct Node *context, const char *filename, int lineno,
 	 */
 	if (edata->elevel >= ERROR)
 	{
-		errfinish(filename, lineno, funcname);
+		errfinish(filename, lineno, funcname, extname);
 		pg_unreachable();
 	}
 
@@ -708,7 +708,7 @@ errsave_finish(struct Node *context, const char *filename, int lineno,
 	recursion_depth++;
 
 	/* Save the last few bits of error state into the stack entry */
-	set_stack_entry_location(edata, filename, lineno, funcname);
+	set_stack_entry_location(edata, filename, lineno, funcname, extname);
 
 	/* Replace the LOG value that errsave_start inserted */
 	edata->elevel = ERROR;
@@ -798,7 +798,7 @@ set_stack_entry_domain(ErrorData *edata, const char *domain)
 static void
 set_stack_entry_location(ErrorData *edata,
 						 const char *filename, int lineno,
-						 const char *funcname)
+						 const char *funcname, const char *extname)
 {
 	if (filename)
 	{
@@ -817,6 +817,7 @@ set_stack_entry_location(ErrorData *edata,
 	edata->filename = filename;
 	edata->lineno = lineno;
 	edata->funcname = funcname;
+	edata->extname = extname;
 }
 
 /*
@@ -1903,7 +1904,7 @@ ThrowErrorData(ErrorData *edata)
 	recursion_depth--;
 
 	/* Process the error. */
-	errfinish(edata->filename, edata->lineno, edata->funcname);
+	errfinish(edata->filename, edata->lineno, edata->funcname, edata->extname);
 }
 
 /*
@@ -2000,7 +2001,7 @@ pg_re_throw(void)
 		 */
 		error_context_stack = NULL;
 
-		errfinish(edata->filename, edata->lineno, edata->funcname);
+		errfinish(edata->filename, edata->lineno, edata->funcname, edata->extname);
 	}
 
 	/* Doesn't return ... */
@@ -3110,6 +3111,20 @@ log_status_format(StringInfo buf, const char *format, ErrorData *edata)
 				else
 					appendStringInfoString(buf, unpack_sql_state(edata->sqlerrcode));
 				break;
+			case 'E':
+				{
+					const char *extname = edata->extname;
+
+					if (!extname)
+						extname = "postgres";
+
+					if (padding != 0)
+						appendStringInfo(buf, "%*s", padding,
+										 extname);
+					else
+						appendStringInfoString(buf, extname);
+					break;
+				}
 			case 'Q':
 				if (padding != 0)
 					appendStringInfo(buf, "%*lld", padding,
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index eafa0128ef0..873868b6a15 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -36,6 +36,7 @@
 #include "storage/fd.h"
 #include "storage/shmem.h"
 #include "utils/hsearch.h"
+#include "utils/memutils.h"
 
 
 /* signature for PostgreSQL-specific library init function */
@@ -268,6 +269,35 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			{
+				const char **extname;
+				char *libname_short;
+
+				extname = dlsym(file_scanner->handle,
+								"Pg_extension_filename");
+				if (!extname)
+					elog(ERROR, "couldn't find Pg_extension_filename");
+
+				/*
+				 * For loaded libraries, the filename, without file ending,
+				 * ought to be unique. Pg_extension_filename is intended for
+				 * logging, a full file path would be onerous.
+				 */
+				libname_short = last_dir_separator(libname) + 1;
+				libname_short = MemoryContextStrdup(TopMemoryContext,
+													last_dir_separator(libname) + 1);
+
+				for (int i = strlen(libname_short); i >= 0; i--)
+				{
+					if (libname_short[i] == '.')
+					{
+						libname_short[i] = '\0';
+						break;
+					}
+				}
+				*extname = MemoryContextStrdup(TopMemoryContext, libname_short);
+			}
 		}
 		else
 		{
-- 
2.44.0.279.g3bd955d269

Reply via email to